Python如何将装饰器定义为类


Posted in Python onJuly 30, 2020

问题

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

解决方案

为了将装饰器定义成一个实例,你需要确保它实现了 __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 相关文章推荐
简介Python中用于处理字符串的center()方法
May 18 Python
python将ansible配置转为json格式实例代码
May 15 Python
python3+PyQt5重新实现QT事件处理程序
Apr 19 Python
python查看模块,对象的函数方法
Oct 16 Python
python实现公司年会抽奖程序
Jan 22 Python
python使用wxpy轻松实现微信防撤回的方法
Feb 21 Python
详解Python 调用C# dll库最简方法
Jun 20 Python
django搭建项目配置环境和创建表过程详解
Jul 22 Python
基于python框架Scrapy爬取自己的博客内容过程详解
Aug 05 Python
python如何实现单链表的反转
Feb 10 Python
详解Python 中的 defaultdict 数据类型
Feb 22 Python
python glom模块的使用简介
Apr 13 Python
python实现mask矩阵示例(根据列表所给元素)
Jul 30 #Python
Python3爬虫发送请求的知识点实例
Jul 30 #Python
详解Python 最短匹配模式
Jul 29 #Python
Python如何给你的程序做性能测试
Jul 29 #Python
Python3爬虫中关于中文分词的详解
Jul 29 #Python
Python3爬虫中pyspider的安装步骤
Jul 29 #Python
关于Python3爬虫利器Appium的安装步骤
Jul 29 #Python
You might like
解析PHP 使用curl提交json格式数据
2013/06/29 PHP
ThinkPHP实现将SESSION存入MYSQL的方法
2014/07/22 PHP
php自定义加密与解密程序实例
2014/12/31 PHP
PHP微信支付开发实例
2016/06/22 PHP
php脚本守护进程原理与实现方法详解
2017/07/20 PHP
csdn 博客的css样式 v3
2009/02/24 Javascript
JQuery触发事件例如click
2013/09/11 Javascript
JS 屏蔽键盘不可用与鼠标右键不可用的方法
2013/11/18 Javascript
自制的文件上传JS控件可支持IE、chrome、firefox etc
2014/04/18 Javascript
Bootstrap表格和栅格分页实例详解
2016/05/20 Javascript
AngularJS中如何使用echart插件示例详解
2016/10/26 Javascript
用Nodejs搭建服务器访问html、css、JS等静态资源文件
2017/04/28 NodeJs
javascript cookie的基本操作(添加和删除)
2017/07/24 Javascript
JS实现点击循环切换显示内容的方法
2017/10/19 Javascript
Angular动画实现的2种方式以及添加购物车动画实例代码
2018/08/09 Javascript
jQuery实现ajax的嵌套请求案例分析
2019/02/16 jQuery
python高并发异步服务器核心库forkcore使用方法
2013/11/26 Python
Python中的jquery PyQuery库使用小结
2014/05/13 Python
python写xml文件的操作实例
2014/10/05 Python
python生成圆形图片的方法
2020/03/25 Python
解决pyqt5中QToolButton无法使用的问题
2019/06/21 Python
Django通用类视图实现忘记密码重置密码功能示例
2019/12/17 Python
Win系统PyQt5安装和使用教程
2019/12/25 Python
python将音频进行变速的操作方法
2020/04/08 Python
Python join()函数原理及使用方法
2020/11/14 Python
戴森香港官方网站:Dyson香港
2021/02/11 全球购物
error和exception有什么区别
2012/10/02 面试题
威盛公司软件C++工程师笔试题面试题
2012/07/16 面试题
毕业生个人的自我评价优秀范文
2013/10/03 职场文书
打架检讨书800字
2014/01/10 职场文书
家长建议怎么写
2014/05/15 职场文书
高中军训的心得体会
2014/09/01 职场文书
医德医风个人工作总结2014
2014/11/14 职场文书
归元寺导游词
2015/02/06 职场文书
2016天猫双十一广告语
2016/01/28 职场文书
Android Flutter实现图片滑动切换效果
2022/04/07 Java/Android