本文是装饰器相关内容的第二篇,接上一篇python函数装饰器详解。
函数装饰器装饰方法
函数装饰器装饰普通函数已经很容易理解了:
@decorator
def func():...
#等价于
def func():...
func = decorator(func)
如果装饰器是带参装饰器,那么等价的形式大概是这样的(和装饰器的编码有关,但最普遍的编码形式如下):
@decorator(x, y, z)
def func():...
# 等价于
def func():...
func = decorator(x, y, z)(func)
这样的函数装饰器也可以去装饰类中的方法。看下面的方法装饰形式:
class cls:
@decorator
def method(self,arg1,arg2):
...
它等价于:
class cls:
def method(self,arg1,arg2):
...
method = decorator(method)
在decorator的编码中,仍然像普通的函数装饰器一样编写即可。例如:
def decorator(F):
@wraps(F)
def wrapper(*args, **kwargs):
... # args[0] = self_instance
# args[1]开始才是手动传给method的参数
return wrapper
但必须要考虑到method的第一个参数self,所以包装器wrapper()的第一个参数也是self。
如此一来,函数装饰器既可以装饰函数,又可以装饰方法。
下面是一个示例:
from functools import wraps
def decorator(F):
@wraps(F)
def wrapper(*args, **kwargs):
result = F(*args, **kwargs)
print(args)
return result
return wrapper
@decorator
def func(x,y):
return x + y
print(func(3, 4))
print("-" * 30)
class cls:
@decorator
def method(self, x, y):
return x + y
c = cls()
print(c.method(3, 4))
输出结果:
(3, 4)
7
------------------------------
(<__main__.cls object at 0x01DF1C50>, 3, 4)
7
让类称为装饰器
不仅函数可以作为装饰器,类也可以作为装饰器去装饰其它对象。
如何让类作为装饰器
要让类作为装饰器,先看装饰的形式:
class Decorator:
...
@Decorator
def func():
...
func(arg1, arg2)
如果成功装饰,那么它等价于:
def func(): ...
func = Decorator(func)
func(arg1, arg2)
这和函数装饰器看上去是一样的,但区别在于Decorator这里是一个类,而不是函数,且Decorator(func)
表示的是创建一个Decorator类的实例对象,所以这里赋值符号左边的func是一个对象。所有后面的func(arg1, arg2)
是调用对象,而不是调用函数。
要让实例对象成为可调用对象,它必须实现__call__
方法,所以应该在Decorator类中定义一个__call__
。而且每次调用实例对象的时候,都是在调用__call__
,这里的__call__
对等于函数装饰器中的包装器wrapper
,所以它的参数和逻辑应当和wrapper一样。
如下:
class Decorator():
def __call__(self, *args, **kwargs):
...
再看func = Decorator(func)
,func是Decorator类创建实例的参数,所以Decorator类还必须实现一个__init__
方法,接受func作为参数:
class Decorator:
def __init__(self, func):
...
def __call__(self, *args, **kwargs):
...
元数据问题
这样的装饰器已经能正常工作了,但是会丢失func的元数据信息。所以,必须使用functools的wraps()保留func的元数据:
from functools import wraps
class Decorator:
def __init__(self, func):
wraps(func)(self)
...
def __call__(self, *args, **kwargs):
...
为什么是wraps(func)(self)
?这里显然不能@wraps(func)
的方式装饰包装器,所以只能使用wraps()的原始函数形式。在wraps()装饰函数包装器wrapper的时候,@wraps(func)
等价于wrapper = wraps(func)(wrapper)
,所以这里wraps(func)(self)
的作用也是很明显的:保留func的元数据,并装饰self。被装饰的self是什么?是Decorator的实例对象,因为Decorator类实现了__call__
,所以self是可调用的,所以这里的self类似于函数装饰器返回的wrapper函数(实际上self是Decorator(func)返回的各个实例对象)。
类作为装饰器的参数问题
虽然self是Decorator的可调用实例对象,但是上面的代码中self并不具有func属性,也就是说无法从self去调用func()函数,这似乎使得整个过程都崩塌了:废了老大的劲去解决各种装饰器上的问题,结果却不能调用被装饰的函数。
有两种方式可以解决这个问题:
- 在
__init__
中使用self.func = func
保留func对象作为装饰器的一个属性
- 在使用wraps()后直接在包装器
__call__
中使用__wrapped__
调用原始func函数
这两种方式其实是等价的,因为self.func
和__wrapped__
都指向原始的函数。
def __init__(self,func):
wraps(func)(self)
self.func = func
def __call__(self, *args, **kwargs):
result = self.func(*args, **kwargs)
#-------------------------------
def __init__(self, func):
wraps(func)(self)
def __call__(self, *args, **kwargs):
result = self.__wrapped__(*args, **kwargs)
但这两种方式都有缺陷,缺陷在于装饰类中方法时。(注:在装饰普通函数、类方法的时候,上面的方式不会出错)
class cls:
@decorator
def method(self, x, y):...
因为self.func
和__wrapped__
装饰cls中的方法时指向的都是cls的类变量(只不过这个属