Python如何将装饰器定义为类
作者:David Beazley 时间:2021-10-21 09:27:56
问题
你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例。 你需要让你的装饰器可以同时工作在类定义的内部和外部。
解决方案
为了将装饰器定义成一个实例,你需要确保它实现了 __call__() 和 __get__() 方法。 例如,下面的代码定义了一个类,它在其他函数上放置一个简单的记录层:
import types
from functools import wraps
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0
def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:
@Profiled
def add(x, y):
return x + y
class Spam:
@Profiled
def bar(self, x):
print(self, x)
在交互环境中的使用示例:
>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls
2
>>> s = Spam()
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3
讨论
将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。
首先,使用 functools.wraps() 函数的作用跟之前还是一样,将被包装函数的元信息复制到可调用实例中去。
其次,通常很容易会忽视上面的 __get__() 方法。如果你忽略它,保持其他代码不变再次运行, 你会发现当你去调用被装饰实例方法时出现很奇怪的问题。例如:
>>> s = Spam()
>>> s.bar(3)
Traceback (most recent call last):
...
TypeError: bar() missing 1 required positional argument: 'x'
出错原因是当方法函数在一个类中被查找时,它们的 __get__() 方法依据描述器协议被调用, 在8.9小节已经讲述过描述器协议了。在这里,__get__() 的目的是创建一个绑定方法对象 (最终会给这个方法传递self参数)。下面是一个例子来演示底层原理:
>>> s = Spam()
>>> def grok(self, x):
... pass
...
>>> grok.__get__(s, Spam)
<bound method Spam.grok of <__main__.Spam object at 0x100671e90>>
>>>
__get__() 方法是为了确保绑定方法对象能被正确的创建。 type.MethodType() 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。 如果这个方法是在类上面来访问, 那么 __get__() 中的instance参数会被设置成None并直接返回 Profiled 实例本身。 这样的话我们就可以提取它的 ncalls 属性了。
如果你想避免一些混乱,也可以考虑另外一个使用闭包和 nonlocal 变量实现的装饰器,这个在9.5小节有讲到。例如:
import types
from functools import wraps
def profiled(func):
ncalls = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal ncalls
ncalls += 1
return func(*args, **kwargs)
wrapper.ncalls = lambda: ncalls
return wrapper
# Example
@profiled
def add(x, y):
return x + y
这个方式跟之前的效果几乎一样,除了对于 ncalls 的访问现在是通过一个被绑定为属性的函数来实现,例如:
>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls()
2
>>>
来源:https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p09_define_decorators_as_classes.html
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
oracle快速删除重复的记录
python实现小世界网络生成
![](https://img.aspxhome.com/file/2023/7/112927_0s.jpg)
python3应用windows api对后台程序窗口及桌面截图并保存的方法
Keras中的多分类损失函数用法categorical_crossentropy
![](https://img.aspxhome.com/file/2023/5/63935_0s.jpg)
Python中的zipfile模块使用详解
asp如何直接调用Excel数据?
python 爬取英雄联盟皮肤并下载的示例
![](https://img.aspxhome.com/file/2023/0/60970_0s.png)
python反反爬虫技术限制连续请求时间处理
![](https://img.aspxhome.com/file/2023/2/62542_0s.jpg)
python对站点数据做EOF且做插值绘制填色图
![](https://img.aspxhome.com/file/2023/8/93488_0s.png)
matplotlib事件处理基础(事件绑定、事件属性)
识别操作系统是不是vista的js代码
SQL存储过程初探
![](https://img.aspxhome.com/file/UploadPic/20099/9/cccr-25s.jpg)
python缺失值填充方法示例代码
![](https://img.aspxhome.com/file/2023/1/126931_0s.png)
Python3 获取一大段文本之间两个关键字之间的内容方法
opencv python模糊影像检测效果
![](https://img.aspxhome.com/file/2023/6/80716_0s.jpg)
Python编码类型转换方法详解
Python调用C# Com dll组件实战教程
![](https://img.aspxhome.com/file/2023/8/71748_0s.png)
python 统计列表中不同元素的数量方法
利用OpenCV和Python实现查找图片差异
![](https://img.aspxhome.com/file/2023/8/128778_0s.jpg)