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利用IPython提高开发效率
Aug 10 Python
Scrapy抓取京东商品、豆瓣电影及代码分享
Nov 23 Python
Python实现输出某区间范围内全部素数的方法
May 02 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
Jul 04 Python
python算法与数据结构之单链表的实现代码
Jun 27 Python
python使用Pandas库提升项目的运行速度过程详解
Jul 12 Python
Python识别快递条形码及Tesseract-OCR使用详解
Jul 15 Python
使用python的turtle绘画滑稽脸实例
Nov 21 Python
Keras之自定义损失(loss)函数用法说明
Jun 10 Python
Python基于network模块制作电影人物关系图
Jun 19 Python
ITK 实现多张图像转成单个nii.gz或mha文件案例
Jul 01 Python
pycharm2020.1.2永久破解激活教程,实测有效
Oct 29 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中mysql模块部分功能的简单封装
2011/09/30 PHP
PHP mkdir()无写权限的问题解决方法
2014/06/19 PHP
Yii框架参数配置文件params用法实例分析
2019/09/11 PHP
php5与php7的区别点总结
2019/10/11 PHP
学习从实践开始之jQuery插件开发 菜单插件开发
2012/05/03 Javascript
基于pthread_create,readlink,getpid等函数的学习与总结
2013/07/17 Javascript
jquery ajax 局部无刷新更新数据的实现案例
2014/02/08 Javascript
js replace替换所有匹配的字符串
2014/02/13 Javascript
jQuery Ajax使用FormData对象上传文件的方法
2016/09/07 Javascript
AngularJS实现动态添加Option的方法
2017/05/17 Javascript
Vue.js常用指令的使用小结
2017/06/23 Javascript
jquery使用iscorll实现上拉、下拉加载刷新
2017/10/26 jQuery
vue组件发布到npm简单步骤
2017/11/30 Javascript
layui 表单标签的校验方法
2019/09/04 Javascript
js正则匹配多个全部数据问题
2019/12/20 Javascript
详解Vue的七种传值方式
2021/02/08 Vue.js
python实现的防DDoS脚本
2011/02/08 Python
使用C语言扩展Python程序的简单入门指引
2015/04/14 Python
python实现远程通过网络邮件控制计算机重启或关机
2018/02/22 Python
详解Python如何生成词云的方法
2018/06/01 Python
详解Python连接MySQL数据库的多种方式
2019/04/16 Python
详解Python3网络爬虫(二):利用urllib.urlopen向有道翻译发送数据获得翻译结果
2019/05/07 Python
python flask几分钟实现web服务的例子
2019/07/26 Python
简单了解Python3 bytes和str类型的区别和联系
2019/12/19 Python
口头翻译求职人自荐信
2013/12/07 职场文书
优秀学生自我鉴定范例
2013/12/18 职场文书
小学生期末自我鉴定
2014/01/19 职场文书
音乐教学反思
2014/02/02 职场文书
行政人事专员岗位职责
2014/03/05 职场文书
幼儿园小班评语大全
2014/04/17 职场文书
销售顾问工作计划书
2014/09/15 职场文书
就业协议书盖章的注意事项
2014/09/28 职场文书
2014年远程教育工作总结
2014/12/09 职场文书
药房管理制度范本
2015/08/06 职场文书
优秀家长事迹材料(2016推荐版)
2016/02/29 职场文书
Go gorilla securecookie库的安装使用详解
2022/08/14 Golang