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通过ElementTree操作XML获取结点读取属性美化XML
Dec 02 Python
Python算法之栈(stack)的实现
Aug 18 Python
简单介绍Python下自己编写web框架的一些要点
Apr 29 Python
使用rpclib进行Python网络编程时的注释问题
May 06 Python
Python简单调用MySQL存储过程并获得返回值的方法
Jul 20 Python
python实现unicode转中文及转换默认编码的方法
Apr 29 Python
python3获取当前文件的上一级目录实例
Apr 26 Python
Python之循环结构
Jan 15 Python
浅谈Python3 numpy.ptp()最大值与最小值的差
Aug 24 Python
Python命令行参数解析工具 docopt 安装和应用过程详解
Sep 26 Python
python爬虫数据保存到mongoDB的实例方法
Jul 28 Python
PyQt5的相对布局管理的实现
Aug 07 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 flv视频时间获取函数
2010/06/29 PHP
php技巧小结【推荐】
2017/01/19 PHP
Whatever:hover 无需javascript让IE支持丰富伪类
2010/06/29 Javascript
jQuery点击后一组图片左右滑动的实现代码
2012/08/16 Javascript
浅谈javascript回调函数
2014/12/07 Javascript
jquery心形点赞关注效果的简单实现
2016/11/14 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
Node.js如何对SQLite的async/await封装详解
2019/02/14 Javascript
JavaScript动态添加数据到表单并提交的几种方式
2019/06/26 Javascript
elementui之el-tebs浏览器卡死的问题和使用报错未注册问题
2019/07/06 Javascript
Vue数据绑定实例写法
2019/08/06 Javascript
vue获取form表单的值示例
2019/10/29 Javascript
解决Vue + Echarts 使用markLine标线(precision精度问题)
2020/07/20 Javascript
Python实现动态添加类的属性或成员函数的解决方法
2014/07/16 Python
python通过floor函数舍弃小数位的方法
2015/03/17 Python
在Python的gevent框架下执行异步的Solr查询的教程
2015/04/16 Python
python对html代码进行escape编码的方法
2015/05/04 Python
python实现自动发送邮件发送多人、群发、多附件的示例
2018/01/23 Python
详解TensorFlow在windows上安装与简单示例
2018/03/05 Python
python样条插值的实现代码
2018/12/17 Python
解决python中用matplotlib画多幅图时出现图形部分重叠的问题
2019/07/07 Python
pytorch 输出中间层特征的实例
2019/08/17 Python
Python过滤序列元素的方法
2020/07/31 Python
css3动画 小球滚动 js控制动画暂停
2019/11/29 HTML / CSS
英国内衣连锁店:Boux Avenue
2018/01/24 全球购物
信息专业个人的自我评价
2013/12/27 职场文书
运动会横幅标语
2014/06/17 职场文书
支部书记四风问题自我剖析材料
2014/09/29 职场文书
2014年发展党员工作总结
2014/11/12 职场文书
幼儿园综治宣传月活动总结
2015/05/07 职场文书
2015年学校总务处工作总结
2015/05/19 职场文书
董存瑞观后感
2015/06/11 职场文书
新党员入党决心书
2015/09/22 职场文书
解析:创业计划书和商业计划书二者之间到底有什么区别
2019/08/14 职场文书
Python答题卡识别并给出分数的实现代码
2021/06/22 Python
「地球外少年少女」BD发售宣传CM公开
2022/03/21 日漫