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函数的笔记整理
Apr 07 Python
Python进程间通信用法实例
Jun 04 Python
python itchat实现微信好友头像拼接图的示例代码
Aug 14 Python
Python内置模块logging用法实例分析
Feb 12 Python
Python实例方法、类方法、静态方法的区别与作用详解
Mar 25 Python
python logging模块书写日志以及日志分割详解
Jul 22 Python
python 检查数据中是否有缺失值,删除缺失值的方式
Dec 02 Python
python通用读取vcf文件的类(复制粘贴即可用)
Feb 29 Python
如何快速理解python的垃圾回收机制
Sep 01 Python
django有哪些好处和优点
Sep 01 Python
使用Python webdriver图书馆抢座自动预约的正确方法
Mar 04 Python
Python编程源码报错解决方法总结经验分享
Oct 05 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新手上路(十三)
2006/10/09 PHP
PHP COOKIE设置为浏览器进程
2009/06/21 PHP
PHP实现伪静态方法汇总
2016/01/13 PHP
php策略模式简单示例分析【区别于工厂模式】
2019/09/25 PHP
JavaScript基本概念初级讲解论坛贴的学习记录
2009/02/22 Javascript
jquery引用方法时传递参数原理分析
2014/10/13 Javascript
JavaScript获取页面上被选中文字的方法技巧
2015/03/13 Javascript
JS实现带关闭功能的阿里妈妈网站顶部滑出banner工具条代码
2015/09/17 Javascript
vue+vux实现移动端文件上传样式
2017/07/28 Javascript
浅谈VUE监听窗口变化事件的问题
2018/02/24 Javascript
jquery实现的简单轮播图功能【适合新手】
2018/08/17 jQuery
JS中验证整数和小数的正则表达式
2018/10/08 Javascript
微信小程序云开发 生成带参小程序码流程
2019/05/18 Javascript
微信小程序返回箭头跳转到指定页面实例解析
2019/10/08 Javascript
es6数组includes()用法实例分析
2020/04/18 Javascript
微信小程序单选框自定义赋值
2020/05/26 Javascript
[03:00]2018完美盛典_最佳英雄奖
2018/12/17 DOTA
[48:24]完美世界DOTA2联赛PWL S3 Forest vs INK ICE 第一场 12.09
2020/12/12 DOTA
Python实现的文本简单可逆加密算法示例
2017/05/18 Python
Python实现KNN邻近算法
2021/01/28 Python
利用Python实现微信找房机器人实例教程
2019/03/10 Python
Python-openCV读RGB通道图实例
2020/01/17 Python
Python+Appium实现自动化测试的使用步骤
2020/03/24 Python
python实现感知机模型的示例
2020/09/30 Python
python图片合成的示例
2020/11/09 Python
45个非常奇妙的CSS3 特性应用示例
2012/01/01 HTML / CSS
HTML5 embed标签定义和用法详解
2014/05/09 HTML / CSS
菲律宾最大的网上花店和礼品店:PhilFlower.com
2018/02/09 全球购物
美国香薰蜡烛品牌:PADDYWAX
2018/10/06 全球购物
Timberland法国官网:购买靴子、鞋子、衣服、夹克和配饰
2019/11/30 全球购物
C++的几个面试题附答案
2016/08/03 面试题
校长寄语大全
2014/04/09 职场文书
重阳节活动主持词
2015/07/04 职场文书
2016年学校党支部创先争优活动总结
2016/04/05 职场文书
创业计划书之蛋糕店
2019/08/29 职场文书
win11高清晰音频管理器在哪里?win11找不到高清晰音频管理器解决办法
2022/04/08 数码科技