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随机生成彩票号码的方法
Mar 05 Python
Django视图和URL配置详解
Jan 31 Python
画pytorch模型图,以及参数计算的方法
Aug 17 Python
python使用if语句实现一个猜拳游戏详解
Aug 27 Python
Python PyInstaller库基本使用方法分析
Dec 12 Python
Django框架之中间件MiddleWare的实现
Dec 30 Python
python实现人脸签到系统
Apr 13 Python
Django自带用户认证系统使用方法解析
Nov 12 Python
python自动从arxiv下载paper的示例代码
Dec 05 Python
Python面向对象之成员相关知识总结
Jun 24 Python
Python保存并浏览用户的历史记录
Apr 29 Python
人工智能深度学习OpenAI baselines的使用方法
May 20 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开发文档 会员收费1期
2012/08/14 PHP
PHP中计算字符串相似度的函数代码
2012/12/29 PHP
PHP实现的构造sql语句类实例
2016/02/03 PHP
thinkPHP实现MemCache分布式缓存功能
2016/03/23 PHP
WebGame《逆转裁判》完整版 代码下载(1月24日更新)
2007/01/29 Javascript
javascript 控制 html元素 显示/隐藏实现代码
2009/09/01 Javascript
COM中获取JavaScript数组大小的代码
2009/11/22 Javascript
jQuery 一个图片切换的插件
2011/10/09 Javascript
THREE.JS入门教程(1)THREE.JS使用前了解
2013/01/24 Javascript
javascript:文字不间断向左移动的实例代码
2013/08/08 Javascript
如何通过javascript操作web控件的自定义属性
2013/11/25 Javascript
javascript轻松实现当鼠标移开时已弹出子菜单自动消失
2013/12/29 Javascript
javascript常用的方法整理
2015/08/20 Javascript
Webpack打包慢问题的完美解决方法
2017/03/16 Javascript
Bootstrap 设置datetimepicker在屏幕上面弹出设置方法
2017/03/21 Javascript
nodejs socket实现的服务端和客户端功能示例
2017/06/02 NodeJs
JS开发自己的类库实例分析
2019/08/28 Javascript
Python实现SVN的目录周期性备份实例
2015/07/17 Python
python开发之文件操作用法实例
2015/11/13 Python
python 根据正则表达式提取指定的内容实例详解
2016/12/04 Python
python实现简单中文词频统计示例
2017/11/08 Python
Python AES加密实例解析
2018/01/18 Python
利用python实现短信和电话提醒功能的例子
2019/08/08 Python
Python matplotlib生成图片背景透明的示例代码
2019/08/30 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
2019/12/12 Python
Python爬虫库requests获取响应内容、响应状态码、响应头
2020/01/25 Python
如何对python的字典进行排序
2020/06/19 Python
python爬虫多次请求超时的几种重试方法(6种)
2020/12/01 Python
CSS3基础(RGBa、text-shadow、box-shadow、border-radius)
2012/11/13 HTML / CSS
德国家具在线:Fashion For Home
2017/03/11 全球购物
澳大利亚领先的女性运动服品牌:Lorna Jane
2020/06/19 全球购物
初中三年毕业生的自我评价分享
2014/02/14 职场文书
幼儿园父亲节活动方案
2014/03/11 职场文书
体育专业求职信
2014/07/16 职场文书
导游词之广州陈家祠
2019/10/21 职场文书
Python如何配置环境变量详解
2021/05/18 Python