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 for Informatics 第11章之正则表达式(二)
Apr 21 Python
python3 实现的人人影视网站自动签到
Jun 19 Python
轻松掌握python设计模式之访问者模式
Nov 18 Python
Python复数属性和方法运算操作示例
Jul 21 Python
python操作mysql代码总结
Jun 01 Python
使用Python进行QQ批量登录的实例代码
Jun 11 Python
django模型动态修改参数,增加 filter 字段的方式
Mar 16 Python
Selenium 滚动页面至元素可见的方法
Mar 18 Python
使用python客户端访问impala的操作方式
Mar 28 Python
Python基于Tkinter编写crc校验工具
May 06 Python
python批量处理多DNS多域名的nslookup解析实现
Jun 28 Python
python 如何停止一个死循环的线程
Nov 24 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 接口问题(php接口主要也就是运用curl,curl函数)
2013/07/01 PHP
PHP实现指定字段的多维数组排序函数分享
2015/03/09 PHP
php实现将上传word文件转为html的方法
2015/06/03 PHP
php in_array() 检查数组中是否存在某个值详解
2016/11/23 PHP
PHP PDOStatement::fetchColumn讲解
2019/01/31 PHP
php命令行模式代码实例详解
2021/02/26 PHP
jQuery表单验证插件formValidator(改进版)
2012/02/03 Javascript
用js代码改变单选框选中状态的简单实例
2013/12/18 Javascript
解析jquery中的ajax缓存问题
2013/12/19 Javascript
javascript正则匹配汉字、数字、字母、下划线
2014/04/10 Javascript
jQuery中的height innerHeight outerHeight区别示例介绍
2014/06/15 Javascript
JS实现动态给图片添加边框的方法
2015/04/01 Javascript
jquery实现select选择框内容左右移动代码分享
2015/11/21 Javascript
详解jQuery简单的表格应用
2016/12/16 Javascript
详解AngularJS 模块化
2017/06/14 Javascript
JavaScript变量作用域_动力节点Java学院整理
2017/06/27 Javascript
利用百度地图API获取当前位置信息的实例
2017/11/06 Javascript
webpack分离css单独打包的方法
2018/06/12 Javascript
浅析JS中NEW的实现原理及重写
2020/02/20 Javascript
js实现简单的无缝轮播效果
2020/09/05 Javascript
Pyramid添加Middleware的方法实例
2013/11/27 Python
Python实现的科学计算器功能示例
2017/08/04 Python
安装python时MySQLdb报错的问题描述及解决方法
2018/03/20 Python
对python 匹配字符串开头和结尾的方法详解
2018/10/27 Python
详细分析Python可变对象和不可变对象
2020/07/09 Python
Python中的With语句的使用及原理
2020/07/29 Python
html5视频媒体标签video的使用方法及完整参数说明详解
2019/09/27 HTML / CSS
大专生简历的自我评价
2013/11/26 职场文书
计算机专业求职信
2014/06/02 职场文书
异地恋情人节寄语
2015/02/28 职场文书
召开会议通知范文
2015/04/15 职场文书
爱鸟护鸟的宣传语
2015/07/13 职场文书
2016年秋季运动会加油稿
2015/12/21 职场文书
2016领导干部廉洁自律心得体会
2016/01/13 职场文书
SQL Server内存机制浅探
2022/04/06 SQL Server
Windows Server 2019 安装DHCP服务及相关配置
2022/04/28 Servers