python装饰器
1.函数作用域
1)访问变量时,会首先寻找本作用域是否存在该变量,若没有,则依次寻找外部作用域。
val = 'test'
def foo():
print(id(val))
foo()
print(id(val))
运行结果:38638064 38638064
2)若在本作用域内,赋值一个变量,则该变量是一个全新的局部变量,与外部作用域变量毫无关系
val = 'test'
def foo():
val = 'hello world'
print(id(val))
foo()
print(id(val))
运行结果: 38141744 38900208
同理,以下示例会报错
val = 'test'
def foo():
print(id(val))
val = 'hello world'
print(id(val))
foo()
print(id(val))
运行结果:
UnboundLocalError: local variable 'val' referenced before assignment
因为,作用域内有赋值,则val在该作用域内是个全新的局部变量,和外部val没有丝毫关系,第一个print(id(val)),会先找到本作用域内定义的val,因此会报上述错误。
2.函数的闭包
def foo():
val = 'test'
print(id(val))
def inner():
print(id(val))
return inner
inner = foo()
inner()
运行结果:35428200 35428200
根据上节说的函数作用域,输出结果应该是相同的,没有问题。
问题的关键是,作用域是有范围的,当一个函数执行完毕后,作用域内的对象都释放了。
为什么执行inner()还能访问到外部作用域内的val变量呢?
这就是函数的闭包特性,嵌套函数能够记住它所处的封闭的命名空间(封闭命名空间内的对象不会释放)。
如何查看内嵌函数的闭包空间呢?可以通过函数的__closure__属性。
print(inner.__closure__)
运行结果:
7575400
7575400
(<cell at 0x00000000006B5618: str object at 0x0000000000739768>,)
0x0000000000739768的十进制值是7575400
嵌套函数使用外部函数参数:
def foo(x):
print(id(x))
def inner():
print(id(x))
return inner
inner = foo(1)
inner()
print(inner.__closure__)
运行结果:
1562953392
1562953392
(<cell at 0x0000000002135618: int object at 0x000000005D28C6B0>,)
可见外部函数传入的参数(嵌套函数有使用的)也会存在于闭包空间。
3.装饰器
上例中,如果参数x也是一个函数,如下所示:
def foo(x):
def inner():
return x() + 1
return inner
def myfunc():
return 1
mynewfunc = foo(myfunc)
result = mynewfunc()
print(result)
运行结果:2
内嵌函数对我们的主函数myfunc逻辑重新组织了一番,并且执行外部函数,返回这个新的内嵌函数mynewfunc,替换掉我们的主函数myfunc
执行新的函数mynewfunc即执行内嵌函数,因为闭包空间的存在,调用myfunc函数,并加上1,所以执行结果为2
外部函数foo,就被称作装饰器(重新装饰主函数myfunc,并返回装饰后的函数mynewfunc)
作用是对主函数,进行修饰,减少了主函数代码的复杂性,如增加log等辅助操作。
4.python装饰器符@
在python中,使用装饰器符@,作用于被装饰主函数,特点是简洁明了。
def foo(x):
def inner():
return x() + 1
return inner
@foo
def myfunc():
return 1
result = myfunc()
print(result)
使用@直接将装饰器,放在主函数的上面,调用主函数时,会自动进行上节操作,返回一个拥有闭包空间的函数。
5.*args,kwargs
*args,kwargs表示参数列表,使用这两个参数,就可以解决参数可变的问题,让装饰器变得通用了
def logger(func):
def inner(*args, **kwargs):
print('arguments:%s,%s'%(args,kwargs))
return func(*args, **kwargs)
return inner
@logger
def foo1(x,y=1):
return x*y
@logger
def foo2():
return 2
foo1(5,4)
foo2()
运行结果:
arguments:(5, 4),{}
arguments:(),{}
对于带参数的被装饰函数,装饰函数参数包含被装饰函数名,参数列表(python中可省略)
不管是args类型的参数,还是kwargs类型的参数(存在于闭包空间),都可以顺利使用装饰器。
6.functools.wraps装饰器
当我们使用装饰器时,实际上是用装饰器重新包装了一个函数,包装后的函数,函数名等都会发生变化。
如何保证函数名这些属性不变呢?使用functools.wraps装饰器,将原函数作为参数,更新装饰器的属性。
def log(func):
def with_log(*args, **kwargs):
print('called..%s' % func.__name__)
return func(*args, **kwargs)
return with_log
@log
def add(x):
return x+1
result = add(1)
print(add.__name__)
print('result..%s' % result)
运行结果:
called..add
with_log
result..2
可以看到,add.__name__函数名称变成了with_log,这是因为add=log(add),经过装饰器重新装饰以后,函数名称已经变成了with_log
如何保持函数名这些属性不变呢?
对with_log函数再次进行装饰,将装饰后的函数名称等修改为func对应的属性。
from functools import wraps
def log(func):
@wraps(func)
def with_log(*args, **kwargs):
print('called..%s' % func.__name__)
return func(*args, **kwargs)
return with_log
@log
def add(x):
return x+1
result = add(1)
print(add.__name__)
print('result..%s' % result)
运行结果:
called..add
add
result..2
wraps对with_log重新装饰,属性值更新为func函数的属性值。