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中生成器和yield语句的用法详解
Apr 17 Python
Python编程中的异常处理教程
Aug 21 Python
python开发之str.format()用法实例分析
Feb 22 Python
Python 异常处理的实例详解
Sep 11 Python
python删除不需要的python文件方法
Apr 24 Python
Python实现的个人所得税计算器示例
Jun 01 Python
在Mac上删除自己安装的Python方法
Oct 29 Python
python__new__内置静态方法使用解析
Jan 07 Python
python数据预处理 :样本分布不均的解决(过采样和欠采样)
Feb 29 Python
新建文件时Pycharm中自动设置头部模板信息的方法
Apr 17 Python
Python HTMLTestRunner库安装过程解析
May 25 Python
python的flask框架难学吗
Jul 31 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中的字符串编码转换(自动识别原编码)
2013/07/02 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
2017/06/02 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
laravel使用数据库测试注意事项
2020/04/10 PHP
JS日历 推荐
2006/12/03 Javascript
Node.js(安装,启动,测试)
2014/06/09 Javascript
全面解析Angular中$Apply()及$Digest()的区别
2016/08/04 Javascript
JavaScript用JSONP跨域请求数据实例详解
2017/01/06 Javascript
jQuery源码解读之extend()与工具方法、实例方法详解
2017/03/30 jQuery
ubuntu编译nodejs所需的软件并安装
2017/09/12 NodeJs
vue自定义全局组件(自定义插件)的用法
2018/01/30 Javascript
H5+C3+JS实现五子棋游戏(AI篇)
2020/05/28 Javascript
解决Idea、WebStorm下使用Vue cli脚手架项目无法使用Webpack别名的问题
2019/10/11 Javascript
JS获取当前时间戳方法解析
2020/08/29 Javascript
[02:14]完美“圣”典2016风云人物:xiao8专访
2016/12/01 DOTA
[01:11:37]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第一场 11.19
2020/11/19 DOTA
深入理解Python分布式爬虫原理
2017/11/23 Python
TF-IDF与余弦相似性的应用(一) 自动提取关键词
2017/12/21 Python
Python简单实现的代理服务器端口映射功能示例
2018/04/08 Python
详解Python装饰器
2019/03/25 Python
python如何输出反斜杠
2020/06/18 Python
通过python-pptx模块操作ppt文件的方法
2020/12/26 Python
美国性感内衣店:Yandy
2018/06/12 全球购物
青年教师培训方案
2014/02/06 职场文书
高级工程师英文求职信
2014/03/19 职场文书
五一劳动节活动记录
2014/03/23 职场文书
带病坚持工作事迹
2014/05/03 职场文书
法人授权委托书样本
2014/09/19 职场文书
高中数学课堂教学反思
2016/02/18 职场文书
2016年先进班集体事迹材料
2016/02/26 职场文书
2016年“12.4”法制宣传日活动总结
2016/04/01 职场文书
Oracle笔记
2021/04/05 Oracle
Pytorch 中net.train 和 net.eval的使用说明
2021/05/22 Python
分享mysql的current_timestamp小坑及解决
2021/11/27 MySQL
「女孩的钓鱼慢活」全新版权绘公布
2022/03/21 日漫
Nginx如何获取自定义请求header头和URL参数详解
2022/07/23 Servers