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 08 Python
python实现bucket排序算法实例分析
May 04 Python
Python中规范定义命名空间的一些建议
Jun 04 Python
一篇文章快速了解Python的GIL
Jan 12 Python
Python多继承原理与用法示例
Aug 23 Python
python 设置xlabel,ylabel 坐标轴字体大小,字体类型
Jul 23 Python
python给指定csv表格中的联系人群发邮件(带附件的邮件)
Dec 31 Python
python模拟预测一下新型冠状病毒肺炎的数据
Feb 01 Python
在Anaconda3下使用清华镜像源安装TensorFlow(CPU版)
Apr 19 Python
Python中and和or如何使用
May 28 Python
python是怎么被发明的
Jun 15 Python
Python使用MapReduce进行简单的销售统计
Apr 22 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 实例化类的一点摘记
2008/03/23 PHP
一个PHP缓存类代码(附详细说明)
2011/06/09 PHP
php四种基础算法代码实例
2013/10/29 PHP
那些年我们错过的魔术方法(Magic Methods)
2014/01/14 PHP
destoon会员注册提示“数据校验失败(2)”解决方法
2014/06/21 PHP
高性能PHP框架Symfony2经典入门教程
2014/07/08 PHP
详解WordPress中用于合成数组的wp_parse_args()函数
2015/12/18 PHP
php封装的验证码工具类完整实例
2016/10/19 PHP
php关联数组与索引数组及其显示方法
2018/03/12 PHP
laravel 执行迁移回滚示例
2019/10/23 PHP
基于jQuery实现的菜单切换效果
2015/10/16 Javascript
jQuery插件cxSelect多级联动下拉菜单实例解析
2016/06/24 Javascript
[原创]javascript typeof id==='string'?document.getElementById(id):id解释
2016/11/02 Javascript
使用Node.js给图片加水印的方法
2016/11/15 Javascript
Bootstrap基本插件学习笔记之模态对话框(16)
2016/12/08 Javascript
jQuery使用EasyUi实现三级联动下拉框效果
2017/03/08 Javascript
vue中的自定义分页插件组件的示例
2018/08/18 Javascript
Vue刷新修改页面中数据的方法
2018/09/16 Javascript
使用Layui搭建后台管理界面的操作方法
2019/09/20 Javascript
vue 解决uglifyjs-webpack-plugin打包出现报错的问题
2020/08/04 Javascript
vue中defineProperty和Proxy的区别详解
2020/11/30 Vue.js
详解Python nose单元测试框架的安装与使用
2017/12/20 Python
tensorflow实现对图片的读取的示例代码
2018/02/12 Python
详解Django将秒转换为xx天xx时xx分
2019/09/27 Python
TensorFlow内存管理bfc算法实例
2020/02/03 Python
Python MOCK SERVER moco模拟接口测试过程解析
2020/04/13 Python
10张动图学会python循环与递归问题
2021/02/06 Python
聪明的粉丝购买门票的地方:TickPick
2018/03/09 全球购物
expedia比利时:预订航班+酒店并省钱
2018/07/13 全球购物
康拓普公司Java笔面试
2016/09/23 面试题
销售工作岗位职责
2013/12/24 职场文书
《落花生》教学反思
2014/02/25 职场文书
关于旅游的活动方案
2014/08/15 职场文书
2019XX公司员工考核管理制度!
2019/08/07 职场文书
快消品行业营销模式与盈利模式分享
2019/09/27 职场文书
python中的被动信息搜集
2021/04/29 Python