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实现发送email的几种常用方法
Aug 18 Python
Python实现的下载8000首儿歌的代码分享
Nov 21 Python
Python2.7+pytesser实现简单验证码的识别方法
Dec 29 Python
Python if语句知识点用法总结
Jun 10 Python
Python 3.3实现计算两个日期间隔秒数/天数的方法示例
Jan 07 Python
python 将有序数组转换为二叉树的方法
Mar 26 Python
python 列表输出重复值以及对应的角标方法
Jun 11 Python
pip安装python库的方法总结
Aug 02 Python
详解python路径拼接os.path.join()函数的用法
Oct 09 Python
python实现异常信息堆栈输出到日志文件
Dec 26 Python
新版Pycharm中Matplotlib不会弹出独立的显示窗口的问题
Jun 02 Python
Python控制鼠标键盘代码实例
Dec 08 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中使用循环实现的金字塔图形
2014/11/08 PHP
thinkphp3.2.2实现生成多张缩略图的方法
2014/12/19 PHP
php超快高效率统计大文件行数
2015/07/05 PHP
php使用curl伪造浏览器访问操作示例
2019/09/30 PHP
jQuery的一些注意
2006/12/06 Javascript
两个DIV等高的JS的实现代码
2007/12/23 Javascript
JS高级笔记
2011/07/13 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
Egret引擎开发指南之创建项目
2014/09/03 Javascript
JavaScript中数据结构与算法(二):队列
2015/06/19 Javascript
理解JavaScript的变量的入门教程
2015/07/07 Javascript
JS简单随机数生成方法
2016/09/05 Javascript
基于React实现表单数据的添加和删除详解
2017/03/14 Javascript
Angular排序实例详解
2017/06/28 Javascript
vue2.0使用swiper组件实现轮播效果
2017/11/27 Javascript
基于canvasJS在PHP中制作动态图表
2020/05/30 Javascript
使用Python脚本将绝对url替换为相对url的教程
2015/04/24 Python
Python实现批量检测HTTP服务的状态
2016/10/27 Python
python获取程序执行文件路径的方法(推荐)
2018/04/26 Python
Python实现将Excel转换成为image的方法
2018/10/23 Python
python3.4 将16进制转成字符串的实例
2019/06/12 Python
Python数据可视化 pyecharts实现各种统计图表过程详解
2019/08/15 Python
英国一家专门出售品牌鞋子的网站:Allsole
2016/08/07 全球购物
荷兰网上药店:Drogisterij.net
2019/09/03 全球购物
美国在线面料商店:Fashion Fabrics Club
2020/01/31 全球购物
毕业研究生的自我鉴定
2013/11/30 职场文书
开办化妆品公司创业计划书
2013/12/26 职场文书
英语道歉信范文
2014/01/09 职场文书
运动会广播稿200米
2014/01/27 职场文书
创业资金计划书
2014/02/06 职场文书
高中家长寄语
2014/04/02 职场文书
作文评语集锦大全
2014/04/23 职场文书
三分钟英语演讲稿
2014/04/24 职场文书
2015年招聘工作总结
2014/12/12 职场文书
Oracle 数据仓库ETL技术之多表插入语句的示例详解
2021/04/12 Oracle
Nginx 常用配置
2022/05/15 Servers