7.1 7.1 7.1 装饰器基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或者可调用对象。
示例如下:
>>> @hello
... def target():
... print('running man')
...
>>>
>>> def target():
... print('running man')
...
>>> target = hello(target)
上面两种写法的最终结果一致:上述两个代码片段执行完毕后得到的target不一定是原来那个target函数,而是hello(target)返回的函数。
装饰器通常会把函数替换成另一个函数
>>> def deco(func):
... def inner():
... print('running man')
... return inner
...
>>> @deco
... def target():
... print('running woman')
...
>>> target()
running man
>>> target
<function deco.<locals>.inner at 0x10f6fcc80>
deco返回的是inner函数对象,那么当使用deco装饰target的时候,实际上就会运行inner函数,审查该对象时,也能发现target现在实际上是inner的引用。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性。
装饰器其实只是语法糖,它可以像常规的可调用对象那样调用,其参数是另一个函数。
python何时执行装饰器
装饰器的一个关键特性就是,它们在被装饰的函数定义之后回立刻运行。
>>> registry = []
>>> def register(func):
... print('running register(%s)' % func)
... registry.append(func)
... return func
...
>>> @register
... def test1():
... print('running test1')
...
running register(<function test1 at 0x10f6fcbf8>)
>>>
>>> @register
... def test2():
... print('running test2')
...
running register(<function test2 at 0x10f6fcd08>)
>>> def main():
... print('running main')
... print('registry ->', registry)
... test1()
... test2()
...
>>> if __name__ == '__main__':
... main()
...
running main
registry -> [<function test1 at 0x10f6fcbf8>, <function test2 at 0x10f6fcd08>]
running test1
running test2
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。
装饰器函数与被装饰的函数在同一个模块中定义。实际情况是,装饰器通常是在一个模块中被定义,然后应用在其他的模块中的函数上。
register装饰器返回的函数与通过参数传入的相同。
很多Python Web框架使用这样的装饰器把函数添加到某种中央注册处,例如把url模式映射到生成HTTP响应的函数上的注册处。
多数的装饰器会修改被装饰的函数。通常,他们会定义一个内部函数,然后将其返回,替换被装饰的函数。使用内部函数的代码几乎都要靠闭包才能正确运作。
那么,想要了解闭包,先要理解变量作用域。
变量作用域规则
>>> def f1(a):
... print(a)
... print(b)
...
>>> f1(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f1
NameError: name 'b' is not defined
>>> b = 6
>>> f1(3)
3
6
上面是一个很简单的例子。第一次调用f1时,并没有定义变量b,因此出现报错。
>>> b = 6
>>> def f2(a):
... print(a)
... print(b)
... b = 8
...
>>> f2(2)
2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
在看这个例子,虽然全局中对变量b赋值了,但还是报错了。
原因是Python在编译函数体时,它判断出b是局部变量。因为在函数体中给他赋值了。生成的字节码证实了这种判断,Python会尝试从本地环境中获取b。
如果在函数中赋值时想让解释器把b当成全局的变量,要用global声明:
>>> b = 6
>>> def f2(a):
... global b
... print(a)
... print(b)
... b = 8
...
>>> f2(2)
2
6
>>> b
8
>>> f2(2)
2
8
这样就不会出现报错了。