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通过zlib实现压缩与解压字符串的方法
Nov 19 Python
Windows和Linux下使用Python访问SqlServer的方法介绍
Mar 10 Python
用Python抢过年的火车票附源码
Dec 07 Python
python 读文件,然后转化为矩阵的实例
Apr 23 Python
解决python Markdown模块乱码的问题
Feb 14 Python
python语言元素知识点详解
May 15 Python
python实现差分隐私Laplace机制详解
Nov 25 Python
Pytorch 实现focal_loss 多类别和二分类示例
Jan 14 Python
Python  word实现读取及导出代码解析
Jul 09 Python
详细分析Python collections工具库
Jul 16 Python
详解python日志输出使用配置文件格式
Feb 10 Python
Python如何利用pandas读取csv数据并绘图
Jul 07 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
《忧国的莫里亚蒂》先导宣传图与STAFF公开
2020/03/04 日漫
php数组函数序列 之shuffle()和array_rand() 随机函数使用介绍
2011/10/29 PHP
PHP递归遍历指定目录的文件并统计文件数量的方法
2015/03/24 PHP
制作个性化的WordPress登陆界面的实例教程
2016/05/21 PHP
Laravel最佳分割路由文件(routes.php)的方式
2016/08/04 PHP
PHP XML和数组互相转换详解
2016/10/26 PHP
php中strtotime函数性能分析
2016/11/20 PHP
不懂JavaScript应该怎样学
2008/04/16 Javascript
JavaScript入门教程(6) Window窗口对象
2009/01/31 Javascript
JQuery学习笔记 nt-child的使用
2011/01/17 Javascript
自己动手制作jquery插件之自动添加删除行的实现
2011/10/13 Javascript
JS上传前预览图片实例
2013/03/25 Javascript
javascript代码运行不出来执行错误的可能情况整理
2013/10/18 Javascript
jquery实现点击展开列表同时隐藏其他列表
2015/08/10 Javascript
浅析Bootstrap缩略图组件与警示框组件
2016/04/29 Javascript
bootstrap table实现单击单元格可编辑功能
2017/03/28 Javascript
分享十三个最佳JavaScript数据网格库
2017/04/07 Javascript
vue服务端渲染的实例代码
2017/08/28 Javascript
Vue框架之goods组件开发详解
2018/01/25 Javascript
vue.js通过路由实现经典的三栏布局实例代码
2018/07/08 Javascript
Vuex模块化应用实践示例
2020/02/03 Javascript
JS代码优化的8点建议
2020/02/04 Javascript
python dict 字典 以及 赋值 引用的一些实例(详解)
2017/01/20 Python
Django中日期处理注意事项与自定义时间格式转换详解
2018/08/06 Python
Python安装pycurl失败的解决方法
2018/10/15 Python
python如何爬取网站数据并进行数据可视化
2019/07/08 Python
Django自带的加密算法及加密模块详解
2019/12/03 Python
PyQt5中向单元格添加控件的方法示例
2020/03/24 Python
Perfume’s Club意大利官网:欧洲美妆电商
2019/05/03 全球购物
JAVA和C++的区别
2013/10/06 面试题
财务支持类个人的自我评价
2014/02/14 职场文书
英语教师岗位职责
2014/03/16 职场文书
投标人法定代表人授权委托书格式
2014/09/28 职场文书
2016年小学生寒假总结
2015/10/10 职场文书
生产实习心得体会范文
2016/01/22 职场文书
阿里云服务器部署RabbitMQ集群的详细教程
2022/06/01 Servers