Python如何将装饰器定义为类

 更新时间:2020年07月30日 08:35:32   作者:David Beazley  
这篇文章主要介绍了Python如何将装饰器定义为类,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

问题

你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例。 你需要让你的装饰器可以同时工作在类定义的内部和外部。

解决方案

为了将装饰器定义成一个实例,你需要确保它实现了 __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
>>>

以上就是Python如何将装饰器定义为类的详细内容,更多关于Python将装饰器定义为类的资料请关注脚本之家其它相关文章!

相关文章

  • python字符串替换re.sub()实例解析

    python字符串替换re.sub()实例解析

    这篇文章主要介绍了python字符串替换re.sub()实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • 详解Python中的null是什么

    详解Python中的null是什么

    这篇文章主要介绍了Python中的null是什么,Python中其实没有null这个词,取而代之的是None对象,即特殊类型NoneType,代表空、没有,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • python实现网站用户名密码自动登录功能

    python实现网站用户名密码自动登录功能

    最近接到这样的需求通过网页用户认证登录实现上网,如何实现网站自动登录功能呢,接下来小编给大家带来了python实现网站用户名密码自动登录功能,需要的朋友可以参考下
    2019-08-08
  • Python操作Access数据库基本步骤分析

    Python操作Access数据库基本步骤分析

    这篇文章主要介绍了Python操作Access数据库基本步骤,结合实例形式详细分析了Python针对access操作的具体步骤与相关注意事项,需要的朋友可以参考下
    2016-09-09
  • 如何用python写个模板引擎

    如何用python写个模板引擎

    这篇文章主要介绍了如何用python写个模板引擎,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下
    2021-01-01
  • Pyqt清空某一个QTreeewidgetItem下的所有分支方法

    Pyqt清空某一个QTreeewidgetItem下的所有分支方法

    今天小编就为大家分享一篇Pyqt清空某一个QTreeewidgetItem下的所有分支方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-06-06
  • python 利用panda 实现列联表(交叉表)

    python 利用panda 实现列联表(交叉表)

    这篇文章主要介绍了python 利用panda 实现列联表(交叉表),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Python利用pyHook实现监听用户鼠标与键盘事件

    Python利用pyHook实现监听用户鼠标与键盘事件

    这篇文章主要介绍了Python利用pyHook实现监听用户鼠标与键盘事件,很有实用价值的一个技巧,需要的朋友可以参考下
    2014-08-08
  • 使用Python实现快速复制或剪切文件列表中的所有文件

    使用Python实现快速复制或剪切文件列表中的所有文件

    在程序开发的过程中,处理文件是我们日常工作中一个很重要的环节,所以这篇文章小编就来和大家一起聊聊如何用Python来快速复制或剪切一个文件列表中的所有文件吧
    2025-04-04
  • python matplotlib保存图片太慢如何解决

    python matplotlib保存图片太慢如何解决

    这篇文章主要介绍了python matplotlib保存图片太慢问题的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09

最新评论