Python 使用类写装饰器的小技巧


Posted in Python onSeptember 30, 2018

最近学到了一个有趣的装饰器写法,就记录一下。

装饰器是一个返回函数的函数。写一个装饰器,除了最常见的在函数中定义函数以外,Python还允许使用类来定义一个装饰器。

1、用类写装饰器

下面用常见的写法实现了一个缓存装饰器。

def cache(func):
  data = {}
  def wrapper(*args, **kwargs):
    key = f'{func.__name__}-{str(args)}-{str(kwargs)})'
    if key in data:
      result = data.get(key)
      print('cached')
    else:
      result = func(*args, **kwargs)
      data[key] = result
      print('calculated')
    return result
  return wrapper

看看缓存的效果。

@cache
def rectangle_area(length, width):
  return length * width
rectangle_area(2, 3)
# calculated
# 6
rectangle_area(2, 3)
# cached
# 6

装饰器的@cache是一个语法糖,相当于func = cache(func),如果这里的cache不是一个函数,而是一个类又会怎样呢?定义一个类class Cache, 那么调用func = Cache(func)会得到一个对象,这时返回的func其实是Cache的对象。定义__call__方法可以将类的实例变成可调用对象,可以像调用函数一样调用对象。然后在__call__方法里调用原本的func函数就能实现装饰器了。所以Cache类也能当作装饰器使用,并且能以@Cache的形式使用。

接下来把cache函数改写为Cache类:

class Cache:
  def __init__(self, func):
    self.func = func
    self.data = {}
  def __call__(self, *args, **kwargs):
    func = self.func
    data = self.data
    key = f'{func.__name__}-{str(args)}-{str(kwargs)})'
    if key in data:
      result = data.get(key)
      print('cached')
    else:
      result = func(*args, **kwargs)
      data[key] = result
      print('calculated')
    return result

再看看缓存结果,效果一样。

@Cache
def rectangle_area(length, width):
  return length * width
rectangle_area(2, 3)
# calculated
# 6
rectangle_area(2, 3)
# cached
# 6

2、装饰类的方法

装饰器不止能装饰函数,也经常用来装饰类的方法,但是我发现用类写的装饰器不能直接用在装饰类的方法上。(有点绕…)

先看看函数写的装饰器如何装饰类的方法。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# calculated
# 6
r.area()
# cached
# 6

但是如果直接换成Cache类会报错,这个错误的原因是area被装饰后变成了类的一个属性,而不是方法。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# TypeError: area() missing 1 required positional argument: 'self'
Rectangle.area
# <__main__.Cache object at 0x0000012D8E7A6D30>
r.area
# <__main__.Cache object at 0x0000012D8E7A6D30>

回头再来看看没有装饰器的情况,Python在实例化对象后把函数变成了方法。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width

  def area(self):
    return self.length * self.width

Rectangle.area
# <function Rectangle.area at 0x0000012D8E7B28C8>
r = Rectangle(2, 3)
r.area
# <bound method Rectangle.area of <__main__.Rectangle object

因此解决办法很简单,要用类写的装饰器来装饰类的方法,只需要把可调用对象包装成函数就行。

# 定义一个简单的装饰器,什么也不做,仅仅是把可调用对象包装成函数
def method(call):
  def wrapper(*args, **kwargs):
    return call(*args, **kwargs)
  return wrapper
class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @method
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# calculated
# 6
r.area()
# cached
# 6

或者用@property还能直接把方法变成属性。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @property
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area
# calculated
# 6
r.area
# cached
# 6

总结

用类写装饰器并非什么特别的技巧,一般情况下确实没必要这么写,不过这样就可以用一些类的特性来写装饰器,比如类的继承,也算是提供了另一种思路吧。

Python 相关文章推荐
python数据结构之二叉树的统计与转换实例
Apr 29 Python
在Python的gevent框架下执行异步的Solr查询的教程
Apr 16 Python
Python3.x中自定义比较函数
Apr 24 Python
详细解析Python当中的数据类型和变量
Apr 25 Python
深入理解Python中各种方法的运作原理
Jun 15 Python
python实现随机调用一个浏览器打开网页
Apr 21 Python
Python对象属性自动更新操作示例
Jun 15 Python
Python内置类型性能分析过程实例
Jan 29 Python
Python多进程编程常用方法解析
Mar 26 Python
如何在sublime编辑器中安装python
May 20 Python
10个python爬虫入门基础代码实例 + 1个简单的python爬虫完整实例
Dec 16 Python
python爬取微博评论的实例讲解
Jan 15 Python
浅谈django三种缓存模式的使用及注意点
Sep 30 #Python
使用Python实现租车计费系统的两种方法
Sep 29 #Python
Python实现App自动签到领取积分功能
Sep 29 #Python
10个Python小技巧你值得拥有
Sep 29 #Python
实例分析python3实现并发访问水平切分表
Sep 29 #Python
3个用于数据科学的顶级Python库
Sep 29 #Python
使用Python机器学习降低静态日志噪声
Sep 29 #Python
You might like
WINDOWS下php5.2.4+mysql6.0+apache2.2.4+ZendOptimizer-3.3.0配置
2008/03/28 PHP
PHP 面向对象实现代码
2009/11/11 PHP
PHP array_multisort()函数的使用札记
2011/07/03 PHP
php判断手机访问还是电脑访问示例分享
2014/01/20 PHP
php计划任务之ignore_user_abort函数实现方法
2015/01/08 PHP
php强制用户转向www域名的方法
2015/06/19 PHP
php图片水印添加、压缩、剪切的封装类实现
2020/04/18 PHP
PHP获取本周所有日期或者最近七天所有日期的方法
2018/06/20 PHP
PHP5.5新特性之yield理解与用法实例分析
2019/01/11 PHP
Javascript倒计时代码
2010/08/12 Javascript
jQuery代码优化 事件委托篇
2011/11/01 Javascript
利用JQuery和JS实现奇偶行背景颜色自定义效果
2012/11/19 Javascript
写得不错的jquery table鼠标经过变色代码
2013/09/27 Javascript
js脚本获取webform服务器控件的方法
2014/05/16 Javascript
JavaScript实现的字符串replaceAll函数代码分享
2015/04/02 Javascript
jQuery+PHP星级评分实现方法
2015/10/02 Javascript
DeviceOne 让你一见钟情的App快速开发平台
2016/02/17 Javascript
基于javascript实现样式清新图片轮播特效
2016/03/30 Javascript
实例讲解JavaScript中instanceof运算符的用法
2016/06/08 Javascript
深入分析javascript中console命令
2016/08/14 Javascript
基于iscroll.js实现下拉刷新和上拉加载效果
2016/11/28 Javascript
jQuery 判断元素整理汇总
2017/02/28 Javascript
Node.js 使用jade模板引擎的示例
2018/05/11 Javascript
JS实现横向轮播图(中级版)
2020/01/18 Javascript
vue中后端做Excel导出功能返回数据流前端的处理操作
2020/09/08 Javascript
vue项目中微信登录的实现操作
2020/09/08 Javascript
在Python中的Django框架中进行字符串翻译
2015/07/27 Python
Python去除、替换字符串空格的处理方法
2018/04/01 Python
记一次pyinstaller打包pygame项目为exe的过程(带图片)
2020/03/02 Python
浅谈keras中的目标函数和优化函数MSE用法
2020/06/10 Python
理工大学毕业生自荐信
2013/11/01 职场文书
光荣入党自我鉴定
2014/01/22 职场文书
优秀团员个人事迹材料
2014/01/29 职场文书
2014年班长个人工作总结
2014/11/14 职场文书
2015入党自传格式范文
2015/06/26 职场文书
员工工作失职检讨书范文!
2019/07/03 职场文书