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中扩展包的安装方法详解
Jun 14 Python
Python从数据库读取大量数据批量写入文件的方法
Dec 10 Python
如何在django里上传csv文件并进行入库处理的方法
Jan 02 Python
Django csrf 两种方法设置form的实例
Feb 03 Python
python对csv文件追加写入列的方法
Aug 01 Python
python利用dlib获取人脸的68个landmark
Nov 27 Python
Python退出时强制运行一段代码的实现方法
Apr 29 Python
tensorflow实现残差网络方式(mnist数据集)
May 26 Python
Python ConfigParser模块的使用示例
Oct 12 Python
Django Auth用户认证组件实现代码
Oct 13 Python
Python实现微信表情包炸群功能
Jan 28 Python
Python爬虫设置Cookie解决网站拦截并爬取蚂蚁短租的问题
Feb 22 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
PHP5中的this,self和parent关键字详解教程
2007/03/19 PHP
openflashchart 2.0 简单案例php版
2012/05/21 PHP
PHP的全局错误处理详解
2016/04/25 PHP
SAE实时日志接口SDK用法示例
2016/10/09 PHP
替换php字符串中的单引号为双引号的方法
2017/02/16 PHP
php魔法函数与魔法常量使用介绍
2017/07/23 PHP
js复制到剪切板的实例方法
2013/06/28 Javascript
ECMAScript5中的对象存取器属性:getter和setter介绍
2014/12/08 Javascript
JavaScript中扩展Array contains方法实例
2020/08/23 Javascript
javascript中undefined与null的区别
2015/08/16 Javascript
js 判断各种数据类型的简单方法(推荐)
2016/08/29 Javascript
详解Node.js access_token的获取、存储及更新
2017/06/20 Javascript
js es6系列教程 - 新的类语法实战选项卡(详解)
2017/09/02 Javascript
axios全局请求参数设置,请求及返回拦截器的方法
2018/03/05 Javascript
解决Mac node版本升级失败的问题
2018/05/16 Javascript
在Angular中使用JWT认证方法示例
2018/09/10 Javascript
详解vuex之store拆分即多模块状态管理(modules)篇
2018/11/13 Javascript
微信小程序订阅消息(java后端实现)开发
2020/06/01 Javascript
Postman如何实现参数化执行及断言处理
2020/07/28 Javascript
ES6中的Javascript解构的实现
2020/10/30 Javascript
python处理中文编码和判断编码示例
2014/02/26 Python
python写入中英文字符串到文件的方法
2015/05/06 Python
在Django框架中伪造捕捉到的URLconf值的方法
2015/07/18 Python
解决Python正则表达式匹配反斜杠''\''问题
2019/07/17 Python
python如何操作mysql
2020/08/17 Python
详解pytorch中squeeze()和unsqueeze()函数介绍
2020/09/03 Python
Python类绑定方法及非绑定方法实例解析
2020/10/09 Python
详解CSS3的图层阴影和文字阴影效果使用
2016/06/09 HTML / CSS
phonegap常用事件总结(必看篇)
2017/03/31 HTML / CSS
html5移动端自适应布局的实现
2020/04/15 HTML / CSS
给儿子的表扬信
2014/01/15 职场文书
学雷锋演讲稿
2014/03/04 职场文书
高校自主招生自荐信2015
2015/03/04 职场文书
oracle表分区的概念及操作
2021/04/24 Oracle
详解Python+OpenCV进行基础的图像操作
2022/02/15 Python
golang为什么要统一错误处理
2022/04/03 Golang