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单元测试框架unittest简明使用实例
Apr 13 Python
python学习数据结构实例代码
May 11 Python
Python中转换角度为弧度的radians()方法
May 18 Python
在Python中操作字典之fromkeys()方法的使用
May 21 Python
详解Python爬虫的基本写法
Jan 08 Python
windows下安装python的C扩展编译环境(解决Unable to find vcvarsall.bat)
Feb 21 Python
flask session组件的使用示例
Dec 25 Python
python机器学习包mlxtend的安装和配置详解
Aug 21 Python
pyinstaller打包找不到文件的问题解决
Apr 15 Python
Django与pyecharts结合的实例代码
May 13 Python
python如何实现图片压缩
Sep 11 Python
Windows下pycharm安装第三方库失败(通用解决方案)
Sep 17 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 mysql数据库操作分页类
2008/06/04 PHP
php max_execution_time执行时间问题
2011/07/17 PHP
js 新浪的一个图片播放图片轮换效果代码
2008/07/15 Javascript
jQuery 中使用JSON的实现代码
2011/12/01 Javascript
javascript实现图片切换的幻灯片效果源代码
2012/12/12 Javascript
js局部刷新页面时间具体实现
2013/07/04 Javascript
html页面显示年月日时分秒和星期几的两种方式
2013/08/20 Javascript
探讨js中的双感叹号判断
2013/11/11 Javascript
JavaScript禁止页面操作的示例代码
2013/12/17 Javascript
jQuery实现textarea自动增长宽高的方法
2015/12/18 Javascript
浅谈jquery高级方法描述与应用
2016/10/04 Javascript
JavaScript严格模式详解
2017/01/16 Javascript
Vuejs入门教程之Vue生命周期,数据,手动挂载,指令,过滤器
2017/04/19 Javascript
Javascript继承机制详解
2017/05/30 Javascript
LayUi中接口传数据成功,表格不显示数据的解决方法
2018/08/19 Javascript
使用react render props实现倒计时的示例代码
2018/12/06 Javascript
微信小程序实现获取小程序码和二维码java接口开发
2019/03/29 Javascript
vue项目中使用scss的方法步骤
2019/05/16 Javascript
vue实现的上拉加载更多数据/分页功能示例
2019/05/25 Javascript
vue+iview实现文件上传
2020/11/17 Vue.js
12步教你理解Python装饰器
2016/02/25 Python
python处理html转义字符的方法详解
2016/07/01 Python
Python协程的用法和例子详解
2017/09/09 Python
pandas筛选某列出现编码错误的解决方法
2018/11/07 Python
解决新django中的path不能使用正则表达式的问题
2018/12/18 Python
Python XlsxWriter模块Chart类用法实例分析
2019/03/11 Python
仓库管理制度
2014/01/21 职场文书
庆祝教师节活动方案
2014/01/31 职场文书
建筑专业毕业生自荐信
2014/05/25 职场文书
会计人员岗位职责
2015/02/03 职场文书
劳保用品管理制度范本
2015/08/06 职场文书
《正比例》教学反思
2016/02/23 职场文书
vue基于Teleport实现Modal组件
2021/05/31 Vue.js
Mysql 设置boolean类型的操作
2021/06/04 MySQL
Winsows11性能如何? win11性能测评多核竟比Win10差了10%
2021/11/21 数码科技
微信小程序实现轮播图指示器
2022/06/25 Javascript