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的内建模块collections的教程
Apr 28 Python
python 调用HBase的简单实例
Dec 18 Python
django自带的server 让外网主机访问方法
May 14 Python
基于Python List的赋值方法
Jun 23 Python
Django框架实现的分页demo示例
May 25 Python
python之拟合的实现
Jul 19 Python
python异常处理try except过程解析
Feb 03 Python
python 实现线程之间的通信示例
Feb 14 Python
Pycharm激活码激活两种快速方式(附最新激活码和插件)
Mar 12 Python
DataFrame.groupby()所见的各种用法详解
Jun 14 Python
pycharm中leetcode插件使用图文详解
Dec 07 Python
pycharm部署django项目到云服务器的详细流程
Jun 29 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图片上传程序
2008/03/27 PHP
微信API接口大全
2015/04/15 PHP
javascript 多种搜索引擎集成的页面实现代码
2010/01/02 Javascript
JavaScript Event学习第十章 一些可替换的事件对
2010/02/10 Javascript
combox改进版 页面原型参考dojo的,比网上jQuery的那些combox功能强,代码更小
2010/04/15 Javascript
js禁止页面刷新禁止用F5键刷新禁止右键的示例代码
2013/09/23 Javascript
jquery $.fn $.fx是什么意思有什么用
2013/11/04 Javascript
node.js+express制作网页计算器
2016/01/17 Javascript
JavaScript实现阿拉伯数字和中文数字互相转换
2016/06/12 Javascript
JavaScript实现格式化字符串函数String.format
2016/12/16 Javascript
微信小程序 动态的设置图片的高度和宽度详解及实例代码
2017/02/24 Javascript
vue如何集成raphael.js中国地图的方法示例
2017/08/15 Javascript
Node.js 中使用 async 函数的方法
2017/11/20 Javascript
layui实现table加载的示例代码
2018/08/14 Javascript
从零开始封装自己的自定义Vue组件
2018/10/09 Javascript
解决Vue.js应用回退或刷新界面时提示用户保存修改问题
2019/11/24 Javascript
[01:25]DOTA2超级联赛专访iG 将调整状态找回自己
2013/06/05 DOTA
[03:49]2016完美“圣”典风云人物:AMS专访
2016/12/06 DOTA
Python标准库之多进程(multiprocessing包)介绍
2014/11/25 Python
Python中使用Flask、MongoDB搭建简易图片服务器
2015/02/04 Python
python+matplotlib实现鼠标移动三角形高亮及索引显示
2018/01/15 Python
Python3利用Dlib实现摄像头实时人脸检测和平铺显示示例
2019/02/21 Python
Python函数和模块的使用总结
2019/05/20 Python
Python模块汇总(常用第三方库)
2019/10/07 Python
python的faker库用法
2019/11/28 Python
Pytorch实现的手写数字mnist识别功能完整示例
2019/12/13 Python
python判断变量是否为列表的方法
2020/09/17 Python
详解Python yaml模块
2020/09/23 Python
html5定位并在百度地图上显示的示例
2014/04/27 HTML / CSS
Kaufmann Mercantile官网:家居装饰、配件、户外及更多
2018/09/28 全球购物
群众路线自我剖析及整改措施
2014/11/04 职场文书
英文版辞职信
2015/02/28 职场文书
毕业论文答辩稿范文
2015/06/23 职场文书
html+css实现分层金字塔的实例
2021/06/02 HTML / CSS
python turtle绘图命令及案例
2021/11/23 Python
利用Java连接Hadoop进行编程
2022/06/28 Java/Android