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中利用await关键字如何等待Future对象完成详解
Sep 07 Python
python高阶爬虫实战分析
Jul 29 Python
python3.4爬虫demo
Jan 22 Python
Python3匿名函数lambda介绍与使用示例
May 18 Python
使用Python实现跳帧截取视频帧
May 31 Python
Django中在xadmin中集成DjangoUeditor过程详解
Jul 24 Python
浅谈python3中input输入的使用
Aug 02 Python
Python:slice与indices的用法
Nov 25 Python
python实现飞机大战项目
Mar 11 Python
python高阶函数map()和reduce()实例解析
Mar 16 Python
PyQt5实现登录页面
May 30 Python
Python的logging模块基本用法
Dec 24 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
一个可查询所有表的“通用”查询分页类
2006/10/09 PHP
PHP编码规范之注释和文件结构说明
2010/07/09 PHP
计算php页面运行时间的函数介绍
2013/07/01 PHP
PHP使用ajax的post方式下载excel文件简单示例
2019/08/06 PHP
关于JS管理作用域的问题
2013/04/10 Javascript
javascript中处理时间戳为日期格式的方法
2014/01/02 Javascript
javascript判断chrome浏览器的方法
2014/03/26 Javascript
多个checkbox被选中时如何判断是否有自己想要的
2014/09/22 Javascript
JS解析XML实例分析
2015/01/30 Javascript
基于JavaScript实现Json数据根据某个字段进行排序
2015/11/24 Javascript
再次谈论React.js实现原生js拖拽效果引起的一系列问题
2016/04/03 Javascript
jQuery实现简易的输入框字数计数功能示例
2017/01/16 Javascript
js获取浏览器和屏幕的各种宽度高度
2017/02/22 Javascript
angularjs点击图片放大实现上传图片预览
2017/02/24 Javascript
js判断手机系统是android还是ios
2017/03/07 Javascript
微信小程序获取手机网络状态的方法【附源码下载】
2017/12/08 Javascript
使用jquery DataTable和ajax向页面显示数据列表的方法
2018/08/09 jQuery
Node.js 使用request模块下载文件的实例
2018/09/05 Javascript
详解ES6 系列之异步处理实战
2018/10/26 Javascript
基于vue框架手写一个notify插件实现通知功能的方法
2019/03/31 Javascript
新手如何快速理解js异步编程
2019/06/24 Javascript
vue项目中全局引入1个.scss文件的问题解决
2019/08/01 Javascript
关于layui的动态图标不显示的解决方法
2019/09/04 Javascript
Vue实现多标签选择器
2019/11/28 Javascript
微信小程序实现轨迹回放的示例代码
2019/12/13 Javascript
如何区分vue中的v-show 与 v-if
2020/09/08 Javascript
vuecli项目构建SSR服务端渲染的实现
2020/10/30 Javascript
javascript实现左右缓动动画函数
2020/11/25 Javascript
[14:24]Optic Gaming vs PSG LGD BO3
2018/06/07 DOTA
python线程锁(thread)学习示例
2013/12/04 Python
详解CSS透明opacity和IE各版本透明度滤镜filter的最准确用法
2016/12/20 HTML / CSS
SmartBuyGlasses英国:购买太阳镜和眼镜
2018/01/29 全球购物
法国发饰品牌:Alexandre De Paris
2018/12/04 全球购物
欧姆龙医疗保健与医疗产品:Omron Healthcare
2020/02/10 全球购物
2014年光棍节活动策划方案(创意集锦)
2014/09/29 职场文书
群众路线表态发言材料
2014/10/17 职场文书