装饰器基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后将它返回,或者将其替换成另一个函数或可调用对象
def deco(func): def inner(): print("running innner()") return inner # <1> @deco def target(): # <2> print("running target()") target() # <3>
运行结果:
running innner()
- deco返回inner函数对象
- 使用deco装饰target函数
- 调用被装饰的target函数其实执行的是inner函数
Python装饰器何时执行装饰器
装饰器的一个关键特性是,它在被装饰的函数定义之后立即执行,通常在导入模块或文件时
registry = [] # <1> def register(func): # <2> print('running register(%s)' % func) registry.append(func) return func @register # <3> def f1(): print('running f1()') @register # <4> def f2(): print('running f2()') def f3(): # <5> print('running f3()') def main(): # <6> print('running main()') print('registry ->', registry) f1() f2() f3() if __name__ == '__main__': main()
- 定义一个名为registry的列表
- register函数接收一个函数对象,然后打印这个函数对象,再将函数对象添加到registry列表中,最后返回函数对象
- f1函数被register函数装饰
- f2函数被register函数装饰
- f3没有被register函数装饰
- 打印registry列表并依次执行f1、f2、f3三个函数
如果不看装饰器,应该先打印running main(),再打印registry列表,之后依次执行三个函数,三个函数会打印其函数中的内容,但真实情况是如何呢?我们看一下运行结果:
running register(<function f1 at 0x000000773FBF0730>) running register(<function f2 at 0x000000773FBF38C8>) running main() registry -> [<function f1 at 0x000000773FBF0730>, <function f2 at 0x000000773FBF38C8>] running f1() running f2() running f3()
很遗憾,运行结果和我们一开始的设想不一样,我们看到程序是优先执行register函数,将f1和f2两个函数当做参数传入register函数中,然后register函数打印其传入的函数,之后才打印running main(),再来打印registry列表,这时候registry列表已经不是一个空列表了,在register函数中会把传入的函数参数加入到registry列表中,因为f1和f2被register函数装饰,所以列表有两个函数对象,之后才是依次打印3个函数中的内容
因此我们可以知道,函数装饰器在导入模块时就立即执行,而被装饰的函数只有在明确调用时才运行。事实上,装饰器返回的函数,与通过参数传入所返回的结果相同,大部分装饰器会在内部定义一个函数,然后将其返回
装饰器是一项很有用的技术,很多Python Web框架使用装饰器装饰一个函数,将函数添加到某中央注册处,当客户端发起一个HTTP请求时,通过URL查找相对应的函数,执行函数返回结果
变量作用域规则
我们先来看三个例子:
>>> 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变量没有被定义过,所以f1在打印b的时候报出为定义b的错误
>>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... >>> f2(3) 3 6
我们在函数体之外定义了变量b,这里正常打印了b的值
>>> def f3(a): ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f3 UnboundLocalError: local variable 'b' referenced before assignment
这里输出了3,证明print(a)执行了,但在执行print(b)的时候报错了。是什么原因造成第三个例子无法正常打印b呢?原因是,Python在编译函数的定义体时,它判断b是局部变量,因为在函数中给b赋值了。在执行函数体的时候,Python会尝试从本地环境中获取变量b,但这个时候发现b还没有绑定值
如果想让解释器把b当成全局变量,需要使用global声明:
>>> def f4(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f4(3) 3 6
dis模块会打印Python函数字节码,我们比较一下之前例子的字节码
第一个例子:
>>> from dis import dis >>> dis(f1) 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (a) 4 CALL_FUNCTION 1 6 POP_TOP 3 8 LOAD_GLOBAL 0 (print) 10 LOAD_GLOBAL 1 (b) 12 CALL_FUNCTION 1 14 POP_TOP 16 LOAD_CONST 0 (None) 18 RETURN_VALUE
- 第三行,获取全局变量名称print
- 第四行,从本地环境中获取变量a
- 第九行,从全局变量中获取b
在第一个例子中,不管在函数体或是函数外都没定义b变量,所以获取b变量肯定会报错
而第二个例子中,我们在函数外围定义了变量b,所以第二个函数字节码与第一个类似,但可以正常执行
再来看第三个例子:
>>> b = 6 >>> def f3(a): ... print(a) ... print(b) ... b = 9 ... >>> from dis import dis >>> dis(f3) 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (a) 4 CALL_FUNCTION 1 6 POP_TOP 3 8 LOAD_GLOBAL 0 (print) 10 LOAD_FAST 1 (b) 12 CALL_FUNCTION 1 14 POP_TOP 4 16 LOAD_CONST 1 (9) 18 STORE_FAST 1 (b) 20 LOAD_CONST 0 (None) 22 RETURN_VALUE
第三个例子也是执行失败的例子,我们看第十五行,这里获取变量b并不是LOAD_GLOBAL 从全局环境中获取,而是LOAD_FAST从本地环境获取,这个时候b还未赋值,所以当解释器发现b没有绑定值,就会报出错误