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

标签:Python,装饰器,类
0
投稿

猜你喜欢

  • 对于Python中线程问题的简单讲解

    2023-12-28 10:16:31
  • Python利用字典破解WIFI密码的方法

    2022-08-08 17:18:44
  • 注册表单的改进分解

    2008-05-31 17:19:00
  • php模板引擎技术简单实现

    2023-11-14 13:28:37
  • 深入讲解Python函数中参数的使用及默认参数的陷阱

    2022-04-21 20:09:14
  • Bootstrap每天必学之表单

    2024-05-09 10:39:31
  • python使用Image处理图片常用技巧分析

    2023-01-17 14:51:38
  • 论文查重python文本相似性计算simhash源码

    2023-02-05 18:11:35
  • django框架实现模板中获取request 的各种信息示例

    2023-08-31 05:16:41
  • Go事务中止时是否真的结束事务解析

    2023-07-07 11:35:35
  • fullcalendar日程管理插件月份切换回调处理方案

    2024-05-09 15:25:55
  • 基于Python制作打地鼠小游戏

    2022-04-07 09:13:34
  • 对Pytorch神经网络初始化kaiming分布详解

    2022-08-27 01:39:50
  • JS 中如何判断 null

    2007-12-13 20:28:00
  • python树的同构学习笔记

    2022-10-23 02:06:29
  • 浅析JavaScriptSerializer类的序列化与反序列化

    2024-02-25 17:46:14
  • 通俗解释JavaScript正则表达式快速记忆

    2024-04-22 22:24:04
  • 详解Python程序与服务器连接的WSGI接口

    2021-11-19 03:57:10
  • MySQL数据库之Purge死锁问题解析

    2024-01-28 05:11:50
  • python实现切割url得到域名、协议、主机名等各个字段的例子

    2022-02-26 11:02:25
  • asp之家 网络编程 m.aspxhome.com