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获取beautifulphoto随机某图片代码实例
Dec 18 Python
Python删除空文件和空文件夹的方法
Jul 14 Python
Python实现基于PIL和tesseract的验证码识别功能示例
Jul 11 Python
解决python通过cx_Oracle模块连接Oracle乱码的问题
Oct 18 Python
对matplotlib改变colorbar位置和方向的方法详解
Dec 13 Python
Django 中间键和上下文处理器的使用
Mar 17 Python
python装饰器常见使用方法分析
Jun 26 Python
numpy.transpose()实现数组的转置例子
Dec 02 Python
Python Selenium截图功能实现代码
Apr 26 Python
python设置表格边框的具体方法
Jul 17 Python
python3将变量输入的简单实例
Aug 19 Python
Flask response响应的具体使用
Jul 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
php GD绘制24小时柱状图
2008/06/28 PHP
PHP中VC6、VC9、TS、NTS版本的区别与用法详解
2013/10/26 PHP
PHP SPL标准库之SplFixedArray使用实例
2015/05/12 PHP
详解php比较操作符的安全问题
2015/12/03 PHP
微信公众平台DEMO(PHP)
2016/05/04 PHP
php基于CodeIgniter实现图片上传、剪切功能
2016/05/14 PHP
一份老外写的XMLHttpRequest代码多浏览器支持兼容性
2007/01/11 Javascript
JS左右无缝滚动(一般方法+面向对象方法)
2012/08/17 Javascript
js判读浏览器是否支持html5的canvas的代码
2013/11/18 Javascript
js实现div的切换特效上一个下一个
2014/02/11 Javascript
jQuery实现首页图片淡入淡出效果的方法
2015/06/10 Javascript
JS实现网页Div层Clone拖拽效果
2015/09/26 Javascript
无需 Flash 使用 jQuery 复制文字到剪贴板
2016/04/26 Javascript
jQuery Dialog 取消右上角删除按钮事件
2016/09/07 Javascript
JS实现禁止鼠标右键的功能
2016/10/15 Javascript
详解Vue路由钩子及应用场景(小结)
2017/11/07 Javascript
小程序关于请求同步的总结
2019/05/05 Javascript
微信小程序iBeacon测距及稳定程序的实现解析
2019/07/31 Javascript
VUE实现图片验证码功能
2020/11/18 Javascript
JavaScript实现弹出窗口效果
2020/12/09 Javascript
python冒泡排序算法的实现代码
2013/11/21 Python
Python的Django应用程序解决AJAX跨域访问问题的方法
2016/05/31 Python
python微信跳一跳系列之棋子定位颜色识别
2018/02/26 Python
Python基于生成器迭代实现的八皇后问题示例
2018/05/23 Python
python清除函数占用的内存方法
2018/06/25 Python
把csv文件转化为数组及数组的切片方法
2018/07/04 Python
python正则表达式之对号入座篇
2018/07/24 Python
python爬虫项目设置一个中断重连的程序的实现
2019/07/26 Python
Python利用逻辑回归模型解决MNIST手写数字识别问题详解
2020/01/14 Python
全网最全python库selenium自动化使用详细教程
2021/01/12 Python
英国Radley包德国官网:Radley London德国
2019/11/18 全球购物
Java模拟试题
2014/11/10 面试题
旅游安全协议书
2014/04/21 职场文书
毕业论文评语大全
2014/04/29 职场文书
七夕情人节问候语
2015/11/11 职场文书
2016庆祝教师节新闻稿
2015/11/25 职场文书