网上关于装饰器的文章很多,这里就不多啰嗦了,我总结起来就是两句话:装饰器是一个可调用对象,调用装饰器作用于它要装饰器的对象。装饰器是一个可调用对象,一切可调用对象都可以作为装饰器,函数、方法、类、类的实例等等都可以作为装饰器。
@wrapper
def f(*args, **kwargs):pass
等价于 f = wrapper(f)
@wrapper(0)
def f(*args, **kwargs):pass
等价于 f = wrapper(0)(f)
使用装饰器,记住上面的两句话,就足矣,记住,解释器用装饰器装饰一个对象就做了两件事情,首先调用装饰器,作用于它要装饰器的对象,然后把它装饰的对象所绑定的名字绑定到装饰器返回的对象上,就这点事情,没有其他。
让我们看一个容易让我们犯错的例子,也许它出乎了你的意料,但却是在情理之中
from functools import wraps
def wrapper(f):
@wraps(f)
def inner(*args, **kwargs):
print("call f")
return f(*args, **kwargs)
return inner
@wrapper
def fib(n):
if 0 == n:
return 0
if 1 == n:
return 1
return fib(n - 2) + fib(n - 1)
fib(3)
执行的结果是”call f”被多次打印了,原本我的预期是只打印一次,为什么会打印多次呢,按照fib的定义,它递归调用它自己,可是被装饰之后,它递归调用的不再是它自己了,而是inner,因为装饰之后fib被绑定到inner上了。查看一下字节码就能看出端倪。
从字节码里看出来,调用一个函数的过程是先LOAD_GLOBAL把函数对象压到栈顶(它通过名字fib到全局的namespace中去取得函数对象),压到栈顶,然后把参数入栈,最后调用函数CALL_FUNCTION。
关于生成器,在我上一篇文章里提到,生成器其实就是一个可以多次调用返回的函数,是函数的加强版,之所以如此,是因为python对调用栈的实现不同于c语言/汇编,汇编里面的调用栈是一段连续的内存,使用栈也只借助于几个寄存器,主要是bp和sp,一个指向栈帧的底部,一个指向栈顶,函数调用返回也就是在修改这两个寄存器,根本不可能做到说把一个函数的栈帧出栈然后再入栈,因此在c语言/汇编里面函数是不可能被多次调用返回的,而python不一样,python是把调用栈实现为一个list,函数的frame是从堆上申请的内存,因此它可以被压入栈中,弹出来,然后再压入栈中,这也就有决定了在python中有了生成器这样可以多次调用返回的对象了。frame对象上有一个属性f_back,它指向的是它的调用者的frame。
对生成器的使用,这里提一下抛入一个异常进去的场景,算是我的一个笔记。
1)生成器未启动,throw一个异常进去,会导致生成器终止,异常被抛回来。
2)throw一个异常进去,生成器没有捕获异常,生成器终止,异常被抛回来。
3)throw一个异常进去,生成器捕获了异常,生成器继续。
4)close生成器,生成器捕获了异常,生成器继续执行,直到遇到yield或者return,遇到yield,生成器终止,抛回来一个RunTimeError异常,遇到return,生成器正常终止。