2层装饰器的情况,func函数只能被传到第一层,如果还想接受其他输入参数,还不出错,就要借助functools.partial
来实现1。这种方法我不推荐。
**推荐3层装饰器接受额外参数!**第1层接受额外参数,用(*args)
调用第一层函数,所以第2层就接受到了func
函数。这个装饰器使用时,即使不输入参数,也要用()
调用,保证第2层接受到func。
-
如果同时使用多个装饰器,执行顺序是从内到外。
def z():
相当于z = a(b(c(z)))
,先执行c(z)
然后b(c(z))
然后a(b(c(z)))
。调用时相当于a(b(c(z)))()
。
-
2类class装饰器,具有灵活度大、高内聚、封装性等优点。使用在普通函数上依靠__call__
方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。还有__get__
方法,用于装饰 实例方法,保证self连接到正确的实例(被装饰方法的实例)以使用__call__
。有时希望返回一个可调用的实例,或需要装饰器可以同时工作在类定义的内部和外部。用类装饰器 装饰实例方法时,第一层参数传给__init__
并初始化实例(不懂为什么__init__可以找到正确的self实例),第二层参数传给__call__
函数。(实际上也可以把__new__
重载了接受第一层参数,实例化的时候是先调用__new__
创建实例,如果实例被创建,就自动调用__init__
。但是,__init__
不能加一层wrapper函数,因为 __init__
不允许有返回值!)
被装饰的实例方法变成了装饰器的__call__(self,*args)
调用。但是,在调用__call__
时,先需要 描述器 根据__get__
来把self
参数和实例instance绑定在一起,如果没有重载__get__
去绑定,那么self
参数就不会被和 传入的目标函数的实例 绑定在一起,而一个实例用.
连接外部函数的时候,会变成unbound method
,导致不会自动传入self参数,最后导致传入的*args
占了self的位置,出现少一个参数的TypeError,详情在这 Python添加和绑定方法method到实例instance的小细节。在调用目标函数时手动传入目标函数实例也可以,但是在脚本运行前,经常不知道目标函数名 或者不符合编程习惯,导致容易出错或不优雅。
下面这个例子展示了__init__
,__get__
和__call__
的调用顺序 和对应的实例。代码改编自。
import types
from functools import wraps
class Profiled:
def __init__(self, func):
print('init self= ',self)
print('init func= ',func)
wraps(func)(self)
def __call__(self, *args, **kwargs):
print('call self= ',self)
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, cls):
print('get self= ',self)
print('get instance= ',instance)
if instance is None:
return self
else:
return types.MethodType(self, instance)
print('------before def add-------')
@Profiled
def add(x, y):
return x + y
print('-----before class Spam--------')
class Spam:
@Profiled
def bar(self, x):
print('bar self= ',self, x)
print('-----before s = Spam()--------')
s = Spam()
print('------before s.bar(3)-------')
s.bar(3)
可以看出__init__
在每次读取装饰器的时候运行一次,给了自身实例一个wrapped(func)(self)
到self.__wrapped__
。之后就不再执行。
接下来调用目标函数bar时,先执行__get__
把self
和目标函数实例s绑定在一起(实际上,这里的self虽然写着是Profiled object,但在调用的时候,执行的是__call__
方法。)所以就等于把self.__call__(self,*args)
方法绑定到instance
目标函数实例s上,成为目标函数实例s的buound method。最后传入参数(self=s, 3)
执行self.__wrapped__(*args = s, 3)
。(__call__
把自己的self吃掉了,返回的是和原本func一样的__wrapped__
,用wrap为了保留func的元信息)
也可以考虑绑定Profiled.__call__(self,*args)
类函数,不会自动传入参数,直接返回func。保留元信息,用给__call__加@wraps装饰器?
其实把带参数的装饰器应用到实例instance上,主要考虑self实例参数在哪里接受,正确传入参数就好,该补上参数的就补上就好。
避免使用装饰器类吧。
Decorator inside Python class提供了多种方法,列出了各种情况,但是里面的坑没仔细讲。
-
@functools.wraps(func)
可以保留原始函数的元数据,比如func.__name__
之类的。用在最内层函数的def
的上一行。一般情况都会加上用一下。
import functools
def a():
def b(func):
@functool.wraps(func)
def c(*args, **kwargs):
func(*args, **kwargs)
return c
return b
-
要注意的只是self的位置;还有使用前,需要先实例化,除非这个装饰器是类方法@classmethod
。调用装饰器要写出实例或类的名字。
class A:
def decorator1(self, func):
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
@classmethod
def decorator2(cls, func):
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
a = A()
@a.decorator1
def spam():
print('spam')
spam()
@A.decorator2
def grok():
print('grok')
grok()
需要注意的是,这2个方法装饰器不能在类A
的内部使用,因为在A实例化的过程中,需要先执行装饰器,会导致类A或实例a没有定义的情况。例子改编自9.8 将装饰器定义为类的一部分 - python3-cookbook。
网上说2层装饰器的多,说3层的不多。所以先简单说下2曾装饰器的关键点,有助于理解3曾装饰器。Python装饰器定义时本质上是嵌套函数,但是读取时有特殊之处。先看下面例子的运行结果,和预想的执行顺序有些不同。def b(func): print('b') def c(): print('c') func() # return func() return c@bdef z(): print('z')print('----