python 函数及变量做用域及装饰器decorator @详解

1、函数及变量的做用

 
在python程序中,函数都会建立一个新的做用域,又称为命名空间,当函数遇到变量时,Python就会到该函数的命名空间来寻找变量,由于Python一切都是对象,而在命名空间中,都是以字典形式存在着,这些变量名,函数名都是索引,而值就是,对应的变量值和函数内存地址。在python中能够用globals()查看全局变量,locals()局部变量。python

>>> global_v = '全局变量'
>>> def func():
...         local_v = '局部变量'
...         print(locals())         #调用locals()输出局部变量local_v
>>> func()
{'local_v': '局部变量'}           #命名空间中都是以字典形式保存
>>> print(globals())  
{.........,'global_v': '全局变量', 'func': <function foo at 0x00000092446C7F28>}    #能够看到除了变量,函数名也做为索引,映射函数内存地址,是主程序命名空间的内容

能够看到内置函数globals()返回了一个全部python能识别的变量的字典,而func 拥有本身的命名空间,里面包含了一个{'local_v': '局部变量'}元素数据库

 

2、变量的做用规则

 

  • 在python中的变量做用域规则是:
    1.变量的建立,变量的建立老是会在函数命名空间建立一个新的变量。
    2.变量的访问,是先在函数内部,访问局部变量所在的函数命名空间,当找不到后再到外层,再到整个外层做用域去寻找该变量。 LEGB法则:索变量名的优先级:局部做用域 > 嵌套做用域 > 全局做用域 > 内置做用域
     
    当在函数中使用未肯定的变量名时,Python会按照优先级依次搜索4个做用域,以此来肯定该变量名的意义。首先搜索局部做用域(L),以后是上一层嵌套结构中def或lambda函数的嵌套做用域(E),以后是全局做用域(G),最后是内置做用域(B)。按这个查找原则,在第一处找到的地方中止。找不到就报错。NameError: name 'xxx' is not defined
     
    3.变量的修改,当函数尝试修改外层变量时,这是不行的,函数只能修改本身命名空间的变量。
     若是你非改不可,那只能在变量前面声明global 这样就能够改了
>>> name = 'lina'
>>> age = 22
>>> list_1 = [1, 2, 3]
>>> def fun():
...     name = 'alex'    #1  尝试修改,重赋值alex 给name
...     print(name)
...     print(age)    #2   尝试查找函数命名空间中不存在的变量age, 没找到就去外层做用域找到
...     list_1.append('local')     # 4 此处修改list_1
...     list_1.pop(0)
...     print(list_1)
>>> fun()
'alex'
22
[2, 3, 'local']
>>> print(name)    #3   查看全局变量name 是否被函数修改为功,显然没有
'lina'
>>> print(list_1)
[2, 3, 'local']    #4 此处修改为功

经过上一个例子,咱们能够从#1处看到,尝试给name赋值,在函数中成功了。
但是在#3处发现并无改变name的值,这是由于函数已经开辟内存复制了一份name的值到本身的命名空间中,建立一个同名的新变量name,当fun()运行结束后该内存释放,而在#3处python寻找变量时直接在本身的做用域中找到name = 'lina'。
#2处在自身的内存空间没有找到age变量,就去外层找到age= 22输出。
而在#1处就是所说的函数只能修改自身的变量,#4处对于列表、字典这种,可变对象,传过来的是内存地址,函数是复制了内存地址,而后直接去内存地址修改了,不能同变量混为一谈django

 

3、函数的形参和实参

 
对于Python来讲参数的传递是引用传递(不是值传递),形参名在函数中为局部变量。
对于不可变类型:str、number、tuple命名空间中的复制值改变不会影响外层空间的值。
可是对于可变类型如:list、dict 在函数体中的操做,可能就会改变他的值。

>>> def fun(parameter):     #形式参数
...     parameter = parameter*2
...     print(parameter)
>>> fun(2)     #实际参数
4

 

4、内嵌函数

 
python中的内嵌函数,即在函数内部声明函数,它全部的周期和生命力仍然适用。

>>> def out_fun():
...     a = '外层变量'
...     def inner():
...         print(a)   #1
...     inner()     #2
>>> out_fun()
外层变量
  • 处inner搜索自身命名空间,没找到变量a而后在外层的out_fun的局部变量中寻找到,inner做为内嵌函数拥有访问外层做用域的权限(有读和修改的权限-指复制到自身命名空间后修改)当函数调用结束就释放了。
  • 处函数out_fun在自身命名空间中找到变量名'inner',拿到内存地址而后执行函数

 

5、Python中的闭包

 
咱们如今把内嵌函数做为out_fun的返回值,当out_fun()被执行时,就会定义inner函数,而后返回给fun变量

>>> def out_fun():
...     a = 'out变量'
...     def inner():
...         print(a)  #1
...     return inner
>>> fun = out_fun()
>>> fun.__closure__
(<cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088>,)

如今来理解下这个函数,若是按照变量的做用域规则,在#1处inner首先会在本身的命名空间中去寻找变量a,没找到而后再去外层out_fun寻找。
因此当咱们执行由out_fun()返回的fun时,按照道理这个程序是会报错的。由于当out_fun()执行完毕后就会释放内存,a变量就不存在了,因此当你执行fun时,inner没法找到a变量就会报错。咱们试试看结果如何:安全

>>> def out_fun():
...     a = 'out变量'
...     def inner():
...         print(a) 
...     return inner
>>> fun = out_fun()
>>> fun()
out变量

程序并无报错,这并不矛盾,由于python支持一个名为闭包的特性,从fun.__closure__属性咱们看见了,cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088,
即在不是全局的定义中,定义函数inner(即嵌套函数)时,会记录下外层函数的命名空间,这个对象就保存在.__closure__属性中,去这儿就是找到外层函数的命名空间。cookie

 

6、装饰器

 
装饰器的核心原理就是上面咱们理解到的了。装饰器是一个以函数做为参数并返回一个替换函数的可执行函数。

>>> def out_fun(fun):         #1接受函数做为参数
...     def inner(a, b= 0, *args):
...         print('装饰器先运行0.0')
...         result = fun(a) + b         #2运行传过来的被装饰函数
...         print('装饰后结果为:',result)
...         return result
...     return inner
>>> def foo(x):         #3定义foo函数
...     print('---------------\n这是被装饰函数')
...     result = 2*x
...     print('被装饰函数执行结果为:{}\n--------------'.format(result))
...     return 2*x
>>> decorate_foo = out_fun(foo)         #4将foo函数做为jout_fun参数执行out_fun
>>> foo =decorate_foo         #把装饰过的foo函数decorate_foo 重赋值给foo,再调用foo()
>>> foo()
装饰器先运行0.0
---------------
这是被装饰函数
被装饰执行结果为:4
---------------
装饰后结果为: 2

如今来理解下这段程序,#1处定义了一个函数,他只接受函数做为参数,#2出运行传过来的被装饰函数,#3定义了一个函数,#4处将#3定义的foo做为参数传给out_fun(foo)获得被装饰后decorate_foo,而后再将装饰后的函数从新赋值给foo,而后当你再次运行foo函数的时候,永远都是获得被装饰后的结果了。
讲到这儿就说个实际应用列子吧!
如汽车在公路上行驶,按照某地交通规则,在国道上限速最高80迈,无下限,高速公路上最低60迈最高120迈。
咱们原始程序,经过测速传感器传来的参数计算出汽车当前速度,并返回该速度。session

>>> status = 1
>>> def car_speed(angular_speed, radius = 0.35)  #根据传来的角速度参数,以及半径计算出当前速度
...     current_speed = angular_speed*radius*3.6
...     return current_speed
>>> 
>>> def slowdown():
...     pass   #假设调用此函数是调用刹车、减速系统,会减慢汽车速度
>>>
>>> def decorate_fun(fun):
...     def inner(*args, **kwargs):
...         current_speed = fun(args[0]) if len(args) = 1 else fun(args[0], radius = args[1])
...         if current_speed >110:
...             sys.stdout.write('您已超速!')
...             sys.stdout.flush()
...         elif current_speed > 160:
...             sys.stdout.write('超速50%系统已限速,请注意安全')
...             sys.stdout.flush()
...             slowdown()
...         elif current_speed < 60:
...             sys.stdout.write('该路段限速60,请注意')
...             sys.stdout.flush()
...         else: pass
...         return current_speed
...     return inner
>>> 
>>> decorator_car_speed = decorate_fun(car_speed)
>>> decorato_car_speed(120)
您已超速!

这段程序,当汽车在国道等非限速区域是,直接调用car_speed()函数就能够获得速度,而当行驶上高速公路后,就存在边界值问题,咱们可使用装饰后的decorate_car_speed()函数来处理。闭包

 

7、装饰器符号@ 的应用

 
经过前面已经了解了装饰器原理了,这儿就简单说下@ 的应用。@ 只是python的一种语法糖而已,让程序看起更美观,简洁

>>> def decorator_foo(fun):
...     def inner(*args, **kwargs):
...         fun(*args, **kwargs)
...         pass
...     return inner
>>>
>>> @decorator_foo         #1
>>> def foo(*args, **kwargs):         #2
...     pass
>>>

在#1处@decorator_foo 使用@符号+装饰器函数,在被装饰函数的上方,记住必定要正上方挨着不能空行,就等于前面所学的decorator = decorator_foo(foo) + foo = decorator() 这样之后你调用foo就是调用的被装饰后的foo了app

 

8、讲一个厉害的装饰器应用

 

  • 情形和需求是这样的,好比我在django view 下作用户验证(不用session),有home函数处理普通用户请求,index处理管理员请求,bbs返回论坛请求,member处理会员请求。函数

  • 固然咱们若是在每个函数内都作一次验证,那代码重复就太多了,因此选择用装饰器,不失为一个好方法。但是如今们要求,根据不一样的函数,home、bbs、member都在本地数据库验证,而index作ldap验证,意思就是咱们要在一个装饰器里面,根据不一样的函数作不一样的验证。spa

通常的验证:

def _authentication(r):
    print('假设使用这个函数作本地用户认证,过了返回True,错误返回False')
    return #返回验证结果

def auth(fun):         #装饰器函数
    def wrapper(request, *args, **kwargs):
        if _authentication(request):         #调用验证函数
            result = fun(request)
            return result
        else:
            return '用户名或密码错了,从新登陆吧!'
    return wrapper

@auth
def index(request):
    pass

@auth
def home(request):
    pass

@auth
def bbs(request):
    pass

@auth
def member(request):
    pass

所有代码我就不写了,太多复杂了,就用伪代码,逻辑描述来代替了。
能够看出来,咱们这个函数能够实现用户验证功能,无论你使用cookie也好,去本地数据库取数据也罢。可是咱们上面说的需求,把index来的请求分离出来,作ldap验证,显然这样的装饰器是无法作到的。没法识别谁来的请求。

@装饰器还提供了一功能,能解决这个问题,往下看:

def _authentication(r):
    print('假设使用这个函数作本地用户认证,过了返回True,错误返回False')
    return #返回验证结果

def _ldap(r):
    print('ldap验证')    
    return  #返回ldap验证结果

def auth(souce_type):
    #这儿的souce_type参数就是@auth(v)运行时传过来的参数
    def outer(fun):    
        def wrapper(request, *args, **kwargs):
            if souce_type == 'local':     #* 1 若是请求来源标记是'local'就本地验证
                if _authentication(request):
                    result = fun(request)
                    return result
                else:
                    return '用户名或密码错了,从新登陆吧!'
            elif souce_type == 'ldap':    #* 1 若是请求来源标记是'ldap'就ldap验证
                if _ldap(request):
                    return fun(request)
                else:
                    return '用户名或密码错了,从新登陆吧!'
        return wrapper
    return outer
@auth(souce_type = 'ldap')     #3 装饰
def index(request):
    pass

@auth(souce_type = 'local')         #4
def home(request):
    pass
  • 注意#3,#4处,咱们把auth('parameter')加参数运行了一次,而装饰器函数auth里面进行了三层嵌套,auth---->outer----->wrapper,你能够这样理解,原来的@auth @符号会把后面的内容auth运行一次直接就返回了wrapper, 如今,咱们本身把auth('parameter')加参数运行了一次获得outer,@auth(parameter)就等同于 @outer,@符号把后面的outer运行一次后再获得wrapper并赋给被修饰函数,而函数souce_type来源也被咱们带进了装饰器。
相关文章
相关标签/搜索