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判断某个用户对某个文件的权限
Oct 13 Python
使用C++扩展Python的功能详解
Jan 12 Python
Python反转序列的方法实例分析
Mar 21 Python
python程序快速缩进多行代码方法总结
Jun 23 Python
python自定义函数实现最大值的输出方法
Jul 09 Python
Django的用户模块与权限系统的示例代码
Jul 24 Python
浅谈python图片处理Image和skimage的区别
Aug 04 Python
python web框架中实现原生分页
Sep 08 Python
如何解决django-celery启动后迅速关闭
Oct 16 Python
如何在VSCode上轻松舒适的配置Python的方法步骤
Oct 28 Python
Python列表操作方法详解
Feb 09 Python
pandas针对excel处理的实现
Jan 15 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编辑器PhpStrom运行缓慢问题
2017/02/21 PHP
php爬取天猫和淘宝商品数据
2018/02/23 PHP
php微信公众号开发之快递查询
2018/10/20 PHP
TP5框架使用QueryList采集框架爬小说操作示例
2020/03/26 PHP
使用JavaScript库还是自己写代码?
2010/01/28 Javascript
jQuery Ajax方法调用 Asp.Net WebService 的详细实例代码
2011/04/27 Javascript
js或jquery实现页面打印可局部打印
2014/03/27 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
js中通过父级进行查找定位元素
2014/06/15 Javascript
javascript匿名函数实例分析
2014/11/18 Javascript
原生javascript实现图片弹窗交互效果
2015/01/12 Javascript
jQuery解决IE6、7、8不能使用 JSON.stringify 函数的问题
2016/05/31 Javascript
nodeJs链接Mysql做增删改查的简单操作
2017/02/04 NodeJs
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
bootstrap响应式表格实例详解
2017/05/15 Javascript
vue文件树组件使用详解
2018/03/29 Javascript
Bootstarp在pycharm中的安装及简单的使用方法
2019/04/19 Javascript
Vue formData实现图片上传
2019/08/20 Javascript
ES6 Symbol在对象中的作用实例分析
2020/06/06 Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
2020/07/24 Javascript
vue开发chrome插件,实现获取界面数据和保存到数据库功能
2020/12/01 Vue.js
[02:19]DOTA2上海特级锦标赛 观赛指南 Spectator Guide
2016/02/04 DOTA
Python连接mssql数据库编码问题解决方法
2015/01/01 Python
使用Python实现博客上进行自动翻页
2017/08/23 Python
Python3简单实例计算同花的概率代码
2017/12/06 Python
Python基于FTP模块实现ftp文件上传操作示例
2018/04/23 Python
pycharm开发一个简单界面和通用mvc模板(操作方法图解)
2020/05/27 Python
终于搞懂了Keras中multiloss的对应关系介绍
2020/06/22 Python
基于Django集成CAS实现流程详解
2020/11/28 Python
Expedia爱尔兰:酒店、机票、租车及廉价假期
2017/01/02 全球购物
vue路由实现登录拦截
2021/03/24 Vue.js
就业自荐信
2013/12/04 职场文书
二手房买卖协议书
2014/04/10 职场文书
社区矫正工作方案
2014/06/04 职场文书
Python 匹配文本并在其上一行追加文本
2022/05/11 Python
JavaScript parseInt0.0000005打印5原理解析
2022/07/23 Javascript