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命令行交互提示符的方法
Jan 14 Python
python使用socket连接远程服务器的方法
Apr 29 Python
python将每个单词按空格分开并保存到文件中
Mar 19 Python
Python创建普通菜单示例【基于win32ui模块】
May 09 Python
解决tensorflow模型参数保存和加载的问题
Jul 26 Python
python过滤中英文标点符号的实例代码
Jul 15 Python
Django之使用celery和NGINX生成静态页面实现性能优化
Oct 08 Python
tensorflow实现打印ckpt模型保存下的变量名称及变量值
Jan 04 Python
如何使用Python处理HDF格式数据及可视化问题
Jun 24 Python
python输出结果刷新及进度条的实现操作
Jul 13 Python
pycharm Tab键设置成4个空格的操作
Feb 26 Python
python数字转对应中文的方法总结
Aug 02 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
PHP Zip解压 文件在线解压缩的函数代码
2010/05/26 PHP
php使用Jpgraph绘制简单X-Y坐标图的方法
2015/06/10 PHP
php中实现字符串翻转的方法
2017/02/22 PHP
Yii2.0框架behaviors方法使用实例分析
2019/09/30 PHP
用js实现的仿sohu博客更换页面风格(简单版)
2007/03/22 Javascript
JavaScript分析、压缩工具JavaScript Analyser
2014/12/31 Javascript
director.js实现前端路由使用实例
2015/02/03 Javascript
深入理解JavaScript中的对象复制(Object Clone)
2016/05/18 Javascript
基于jQuery的Web上传插件Uploadify使用示例
2016/05/19 Javascript
防止页面url缓存中ajax中post请求的处理方法
2017/10/10 Javascript
js+css实现红包雨效果
2018/07/12 Javascript
微信小程序与公众号卡券/会员打通的问题
2019/07/25 Javascript
vue实现下拉加载其实没那么复杂
2019/08/13 Javascript
vue 动态创建组件的两种方法
2020/12/31 Vue.js
[28:05]完美世界DOTA2联赛循环赛Inki vs DeMonsTer 第一场 10月30日
2020/10/31 DOTA
关于python的bottle框架跨域请求报错问题的处理方法
2017/03/19 Python
python 将print输出的内容保存到txt文件中
2018/07/17 Python
5 分钟读懂Python 中的 Hook 钩子函数
2020/12/09 Python
实例讲解CSS3中的box-flex弹性盒属性布局
2016/06/09 HTML / CSS
美国大尺码女装零售商:TORRID
2016/10/01 全球购物
芬兰灯具网上商店:Nettilamppu.fi
2018/06/30 全球购物
软件设计的目标是什么
2016/12/04 面试题
经济与贸易专业应届生求职信
2013/11/19 职场文书
正规的求职信范文分享
2013/12/11 职场文书
工程师岗位职责规定
2014/02/26 职场文书
后勤主管岗位职责
2014/03/01 职场文书
教师考核评语
2014/04/28 职场文书
销售顾问工作计划书
2014/08/15 职场文书
党员志愿者活动方案
2014/08/28 职场文书
四风查摆剖析材料
2014/10/10 职场文书
离婚协议书包括哪些内容
2014/10/16 职场文书
优秀团员事迹材料
2014/12/25 职场文书
2015年圣诞节活动总结
2015/03/24 职场文书
2015年预算员工作总结
2015/05/14 职场文书
汤姆索亚历险记读书笔记
2015/06/29 职场文书
go语言-在mac下brew升级golang
2021/04/25 Golang