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 ldap实现登录实例代码
Sep 30 Python
Python HTTP客户端自定义Cookie实现实例
Apr 28 Python
15行Python代码带你轻松理解令牌桶算法
Mar 21 Python
python实现简单登陆流程的方法
Apr 22 Python
Python实现确认字符串是否包含指定字符串的实例
May 02 Python
Python3之不使用第三方变量,实现交换两个变量的值
Jun 26 Python
Python一键安装全部依赖包的方法
Aug 12 Python
原生python实现knn分类算法
Oct 24 Python
使用OpenCV对车道进行实时检测的实现示例代码
Jun 19 Python
Python操作word文档插入图片和表格的实例演示
Oct 25 Python
如何利用python生成MD5并去重
Dec 07 Python
Python使用OpenCV和K-Means聚类对毕业照进行图像分割
Jun 11 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 Web开发MVC框架的Smarty使用说明
2013/04/19 PHP
php笔记之:AOP的应用
2013/04/24 PHP
PHP获取某个月最大天数(最后一天)的方法
2015/07/29 PHP
PHP版本的选择5.2.17 5.3.27 5.3.28 5.4 5.5兼容性问题分析
2016/04/04 PHP
静态html文件执行php语句的方法(推荐)
2016/11/21 PHP
Yii全局函数用法示例
2017/01/22 PHP
PHP 模拟登陆功能实例详解
2019/09/10 PHP
jquery中ajax学习笔记4
2011/10/16 Javascript
jquery提取元素里的纯文本不包含span等里的内容
2013/09/30 Javascript
js改变文章字体大小的实例代码
2013/11/27 Javascript
javascript表单验证和Window详解
2014/12/11 Javascript
Javascript中for循环语句的几种写法总结对比
2017/01/23 Javascript
ionic中的$ionicPlatform.ready事件中的通用设置
2017/06/11 Javascript
Vue2.0 实现移动端图片上传功能
2018/05/30 Javascript
Vue press 支持图片放大功能的实例代码
2018/11/09 Javascript
jquery实现掷骰子小游戏
2019/10/24 jQuery
Vue使用虚拟dom进行渲染view的方法
2019/12/26 Javascript
微信公众号服务器验证Token步骤图解
2019/12/30 Javascript
[01:38]完美世界高校联赛决赛花絮
2018/12/02 DOTA
Python2.6版本中实现字典推导 PEP 274(Dict Comprehensions)
2015/04/28 Python
python使用xslt提取网页数据的方法
2018/02/23 Python
用TensorFlow实现lasso回归和岭回归算法的示例
2018/05/02 Python
python根据list重命名文件夹里的所有文件实例
2018/10/25 Python
Python3.5 Json与pickle实现数据序列化与反序列化操作示例
2019/04/29 Python
Python 把序列转换为元组的函数tuple方法
2019/06/27 Python
Python 时间戳之获取整点凌晨时间戳的操作方法
2020/01/28 Python
python GUI库图形界面开发之PyQt5表格控件QTableView详细使用方法与实例
2020/03/01 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
2020/06/23 Python
Pycharm制作搞怪弹窗的实现代码
2021/02/19 Python
解决CSS3的opacity属性带来的层叠顺序问题
2016/05/09 HTML / CSS
Skip Hop官网:好莱坞宝宝挚爱品牌
2018/06/17 全球购物
Linux如何为某个操作添加别名
2013/03/01 面试题
网络工程师个人的自我评价范文
2013/10/01 职场文书
大学新生军训自我鉴定
2014/03/18 职场文书
详解Python小数据池和代码块缓存机制
2021/04/07 Python
Python 多线程处理任务实例
2021/11/07 Python