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删除列表内容
Aug 04 Python
详解如何在python中读写和存储matlab的数据文件(*.mat)
Feb 24 Python
解决Python 爬虫URL中存在中文或特殊符号无法请求的问题
May 11 Python
python bmp转换为jpg 并删除原图的方法
Oct 25 Python
python3去掉string中的标点符号方法
Jan 22 Python
Django 权限认证(根据不同的用户,设置不同的显示和访问权限)
Jul 24 Python
解决python 读取excel时 日期变成数字并加.0的问题
Oct 08 Python
NumPy中的维度Axis详解
Nov 26 Python
pytorch 利用lstm做mnist手写数字识别分类的实例
Jan 10 Python
tensorflow之变量初始化(tf.Variable)使用详解
Feb 06 Python
Python字典添加,删除,查询等相关操作方法详解
Feb 07 Python
Python如何配置环境变量详解
May 18 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
与数据库连接
2006/10/09 PHP
PHP中对用户身份认证实现两种方法
2011/06/04 PHP
有关phpmailer的详细介绍及使用方法
2013/01/28 PHP
PHP实现数组递归转义的方法
2014/08/28 PHP
微信 开发生成带参数的二维码的实例
2016/11/23 PHP
php+mysql+jquery实现日历签到功能
2017/02/27 PHP
php 截取GBK文档某个位置开始的n个字符方法
2017/03/08 PHP
XHTML-Strict 内允许出现的标签
2006/12/11 Javascript
JavaScript 一道字符串分解的题目
2011/08/03 Javascript
jQuery过滤选择器详解
2015/01/13 Javascript
Bootstrap选项卡与Masonry插件的完美结合
2016/07/06 Javascript
Node.js与MySQL交互操作及其注意事项
2016/10/05 Javascript
原生js实现焦点轮播图效果
2017/01/12 Javascript
vue.js指令和组件详细介绍及实例
2017/04/06 Javascript
vuejs2.0子组件改变父组件的数据实例
2017/05/10 Javascript
Jquery中.bind()、.live()、.delegate()和.on()之间的区别详解
2017/08/01 jQuery
vue中axios处理http发送请求的示例(Post和get)
2017/10/13 Javascript
webpack项目轻松混用css module的方法
2018/06/12 Javascript
create-react-app安装出错问题解决方法
2018/09/04 Javascript
微信小程序登陆注册功能的实现代码
2019/12/10 Javascript
基于JavaScript实现简单扫雷游戏
2021/01/02 Javascript
Python实现去除列表中重复元素的方法小结【4种方法】
2018/04/27 Python
Python3爬虫学习入门教程
2018/12/11 Python
Tensorflow 模型转换 .pb convert to .lite实例
2020/02/12 Python
如何基于python对接钉钉并获取access_token
2020/04/21 Python
一款纯css3实现的颜色渐变按钮的代码教程
2014/11/12 HTML / CSS
关于HTML5语义标签的实践(blog页面)
2016/07/12 HTML / CSS
美国女孩洋娃娃店:American Girl
2017/10/24 全球购物
美国著名的家居用品购物网站:Bed Bath & Beyond
2018/01/05 全球购物
大学生求职简历的自我评价
2013/10/14 职场文书
五四青年节优秀演讲稿范文
2014/05/28 职场文书
大学生村官个人对照检查材料(群众路线)
2014/09/26 职场文书
2014年服务员工作总结
2014/11/18 职场文书
2014年前台文员工作总结
2014/12/08 职场文书
python实现监听键盘
2021/04/26 Python
Oracle用户管理及赋权
2022/04/24 Oracle