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元组操作实例解析
Sep 23 Python
Ubuntu 14.04+Django 1.7.1+Nginx+uwsgi部署教程
Nov 18 Python
Python中字典的基本知识初步介绍
May 21 Python
python实现指定文件夹下的指定文件移动到指定位置
Sep 17 Python
python实现视频分帧效果
May 31 Python
Python 写入训练日志文件并控制台输出解析
Aug 13 Python
利用python实现周期财务统计可视化
Aug 25 Python
python内置函数sorted()用法深入分析
Oct 08 Python
django有外键关系的两张表如何相互查找
Feb 10 Python
Python基础之字典常见操作经典实例详解
Feb 26 Python
Python 的 __str__ 和 __repr__ 方法对比
Sep 02 Python
python爬虫调度器用法及实例代码
Nov 30 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
深入解析fsockopen与pfsockopen的区别
2013/07/05 PHP
destoon调用企业会员公司形象图片的实现方法
2014/08/21 PHP
反射调用private方法实践(php、java)
2015/12/21 PHP
phpstudy后门rce批量利用脚本的实现
2019/12/12 PHP
onpropertypchange
2006/07/01 Javascript
jquery attr 设定src中含有&amp;(宏)符号问题的解决方法
2011/07/26 Javascript
js中对象的声明方式以及数组的一些用法示例
2013/12/11 Javascript
javascript实现节点(div)名称编辑
2014/12/17 Javascript
JavaScript时间转换处理函数
2015/04/14 Javascript
jquery获取复选框的值的简单实例
2016/05/26 Javascript
jQuery Easyui学习教程之实现datagrid在没有数据时显示相关提示内容
2016/07/09 Javascript
AngularJS基础 ng-paste 指令简单示例
2016/08/02 Javascript
微信小程序 实战程序简易新闻的制作
2017/01/09 Javascript
基于node.js依赖express解析post请求四种数据格式
2017/02/13 Javascript
详解vue-router2.0动态路由获取参数
2017/06/14 Javascript
jQuery.Form实现Ajax上传文件同时设置headers的方法
2017/06/26 jQuery
webpack3+React 的配置全解
2017/08/21 Javascript
详解Vue SPA项目优化小记
2018/07/03 Javascript
全面解析vue router 基本使用(动态路由,嵌套路由)
2018/09/02 Javascript
浅谈从React渲染流程分析Diff算法
2018/09/08 Javascript
python 读取excel文件生成sql文件实例详解
2017/05/12 Python
Python实现将HTML转成PDF的方法分析
2019/05/04 Python
pytorch nn.Conv2d()中的padding以及输出大小方式
2020/01/10 Python
解决pycharm每次打开项目都需要配置解释器和安装库问题
2020/02/26 Python
python中温度单位转换的实例方法
2020/12/27 Python
naturalizer加拿大官网:美国娜然女鞋
2017/04/04 全球购物
世界上获奖最多的手机镜头:Olloclip
2018/03/03 全球购物
英国户外服装品牌:Craghoppers
2019/04/25 全球购物
真正的英国宝藏:Mappin & Webb
2019/05/05 全球购物
消防志愿者活动方案
2014/08/23 职场文书
2015年信息中心工作总结
2015/05/25 职场文书
2015年学校远程教育工作总结
2015/07/20 职场文书
教师旷工检讨书
2015/08/15 职场文书
详解gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)
2021/11/27 Vue.js
日元符号 ¥
2022/02/17 杂记
深入理解pytorch库的dockerfile
2022/06/10 Python