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实现的下载8000首儿歌的代码分享
Nov 21 Python
python中的代码编码格式转换问题
Jun 10 Python
Python3使用正则表达式爬取内涵段子示例
Apr 22 Python
在python中利用GDAL对tif文件进行读写的方法
Nov 29 Python
元组列表字典(莫烦python基础)
Apr 03 Python
python 中如何获取列表的索引
Jul 02 Python
python实现从本地摄像头和网络摄像头截取图片功能
Jul 11 Python
Pytorch中的variable, tensor与numpy相互转化的方法
Oct 10 Python
使用python实现数组、链表、队列、栈的方法
Dec 20 Python
Python datetime 如何处理时区信息
Sep 02 Python
python smtplib发送多个email联系人的实现
Oct 09 Python
Python基于argparse与ConfigParser库进行入参解析与ini parser
Feb 02 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/04/25 PHP
PHP使用正则表达式清除超链接文本
2013/11/12 PHP
PHP实现获取某个月份周次信息的方法
2015/08/11 PHP
在WordPress中使用PHP脚本来判断访客来自什么国家
2015/12/10 PHP
php+mysql开发的最简单在线题库(在线做题系统)完整案例
2019/03/30 PHP
laravel5.5安装jwt-auth 生成token令牌的示例
2019/10/24 PHP
JS+XML 省份和城市之间的联动实现代码
2009/10/14 Javascript
javascript解析json数据的3种方式
2014/05/08 Javascript
浅谈javascript中的instanceof和typeof
2015/02/27 Javascript
javascript实时显示北京时间的方法
2015/03/12 Javascript
JS简单实现String转Date的方法
2016/03/02 Javascript
jQuery Dialog对话框事件用法实例分析
2016/05/10 Javascript
javascript设计模式之Adapter模式【适配器模式】实现方法示例
2017/01/13 Javascript
Angular.js中处理页面闪烁的方法详解
2017/03/09 Javascript
Angular2生命周期钩子函数的详细介绍
2017/07/10 Javascript
简单谈谈JS中的正则表达式
2017/09/11 Javascript
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
React中阻止事件冒泡的问题详析
2019/04/12 Javascript
js实现随机抽奖
2020/03/19 Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
2020/05/06 Javascript
vue动画—通过钩子函数实现半场动画操作
2020/08/09 Javascript
Python正确重载运算符的方法示例详解
2017/08/27 Python
python爬虫爬取淘宝商品信息(selenum+phontomjs)
2018/02/24 Python
浅谈selenium如何应对网页内容需要鼠标滚动加载的问题
2020/03/14 Python
Python如何把字典写入到CSV文件的方法示例
2020/08/23 Python
python 绘制正态曲线的示例
2020/09/24 Python
Python实现自动整理文件的脚本
2020/12/17 Python
可自定义箭头样式的CSS3气泡提示框
2016/03/16 HTML / CSS
Shopee越南:东南亚与台湾电商平台
2019/02/03 全球购物
特教教师先进事迹
2014/05/21 职场文书
作文评语集锦
2014/12/25 职场文书
民事答辩状格式范文
2015/05/21 职场文书
导游词之重庆钓鱼城
2019/09/19 职场文书
Mysql数据库命令大全
2021/05/26 MySQL
浅析MySQL如何实现事务隔离
2021/06/26 MySQL
css3带你实现3D转换效果
2022/02/24 HTML / CSS