通俗易懂了解Python装饰器原理


Posted in Python onSeptember 17, 2020

作用

装饰器可以用于用于装饰一个函数或方法,使得在不修改原函数、方法代码的前提下,为方法添加前置或后置操作;

例如突然想要计算一下各个函数的执行时间,又不希望在每一个函数中添加tim.time()来计算执行时间

用法

装饰器的写法网上很多,但是我觉得还是尽量先理解,再知道怎么写会比较好,所以会先说如何理解,在后面重写用法

实现

了解装饰器是如何实现的,远比会写装饰器更重要,简单的说装饰器就是接收一个函数对象,然后先执行前置操作,再执行函数,再执行后置操作;

这么说可能有些抽象,或者举一个不那么恰当的比较贴近生活的例子;

假设你有一台像这样的小风扇:

这台风扇可以充电,有一个开关,打开之后扇叶会旋转,开始工作,当然你也可以插着电打开开关,也可以充好电之后带走,在其他地方打开开关

如果把这个风扇置于一切皆是对象的Python中,风扇就是一个对象,他实现的功能就是出风:

def fengshan():
  return '出风'

为了更好和例子结合,我们用pinyin命名

现在我们实现了一个fengshan函数,返回吹风

如果你稍微有点基础,你就能知道如何调用这个方法

def fengshan():
  return '出风'
print(fengshan())

不要觉得这很基础很墨迹,如果需要理解装饰器,你必须知道,调用函数的方式是函数名称加上括号fengshan()
而这个基础中的基础中的括号()就是执行函数的开关,如果我们不加括号

def fengshan():
  return '出风'
print(fengshan)

返回的将是一个函数对象(例子中的风扇本身)

<function fengshan at 0x7f8e7c4a6950>

这里的意思是 一个叫fengshan的funciont,地址在0x7f8e7c4a6950

那现在我们就可以把风扇带走,在其他地方使用

def fengshan():
  return '出风'
 
func = fengshan
print(fengshan)
print(func)

返回

<function fengshan at 0x7f570eaf3950>
<function fengshan at 0x7f570eaf3950>

这说明func和fengshan是等价的,他们在同一块内存中,所以当我们执行func() 也等价于执行fengshan

def fengshan():
  return '出风'
 
func = fengshan
print('下面是执行fengshan')
print(fengshan())
print('下面是执行func')
print(func())

返回

下面是执行fengshan

出风

下面是执行func

出风

理解到这里之后你也就能理解装饰器的实现了,让我们再看一个例子

def fengshan():
  return '出风'
def wrapper(func):
  return func
print(fengshan)
print(wrapper(fengshan))

这个例子中我们除了保留刚刚一直在用的fengshan函数之外,又定义了一个wrapper

因为python中一切皆是对象,函数也是对象,而函数的入参也可以接收对象,所以函数对象可以作为参数传递给另一个函数wrapper

这个wrapper中什么都没有做,只是返回了接收的func对象,我们打印出来两个对象,可以发现他们其实是同一个对象

<function fengshan at 0x7f9b0c92f950>
<function fengshan at 0x7f9b0c92f950>

现在你就已经理解了装饰器的实现了,而且如果你跟着文中的代码敲一遍,你就已经写了一个装饰器,你只需要稍加修改,比方说,我们在wrapper内部执行接收的func,并且,在前后加上一些操作

def wrapper(func):
  print('在wrapper中执行func前')
  print(func())
  print('在wrapper中执行func后')
wrapper(fengshan)

返回

在wrapper中执行func前

出风

在wrapper中执行func后

如果你觉得很神奇,无法理解,可以回到风扇的图片重新再读一遍,当你理解了上述的代码之后,我们就可以加快速度,完成真正的装饰器的编写

用法

当你成功明白了函数被定义和调用的过程之后,我们开始考虑实际场景,上一段代码中我们还是改变了原有函数的调用

从调用fengshan变成了调用func(fengshan)

我们想要实现不改变原有代码和调用方式的情况下为原有函数添加前置或后置操作,就需要再优化一下我们的装饰器

def fengshan():
  print('出风')
 
 
def wrapper(func):
  def f():
    print('在wrapper中执行func前')
    func()
    print('在wrapper中执行func后')
  return f
 
fengshan = wrapper(fengshan)
fengshan()

为了看到执行过程,我们把fengshan内部的return改为print

返回

在wrapper中执行func前

出风

在wrapper中执行func后

这里我们的改变是在wrapper原有的实现中又包了一层方法f,再回想一下我们前面风扇的例子,现在当我们执行wrapper的时候,执行了什么?

wrapper(func)的返回,是一个函数对象f,这个函数对象的开关没有被打开,f中的代码不会被执行

我们又用同名的fengshan对象去接收了这个函数对象f,在最后一行打开fengshan的开关 -- fengshan(),这时候函数对象f中的代码,才刚被执行

如果看不懂的话,建议从风扇图片开始再看一遍,如果你看懂了,建议你也再看一遍,至此,我们就已经完成了一个装饰器,为了更方便使用装饰器,Python给我们提供了更简便的方法

def wrapper(func):
  def f():
    print('在wrapper中执行func前')
    func()
    print('在wrapper中执行func后')
  return f
@wrapper
def fengshan():
  print('出风')
fengshan()

返回

在wrapper中执行func前

出风

在wrapper中执行func后

补充知识

如果你已经理解了装饰器的执行逻辑,你也就会知道如何让装饰器支持带参数的方法,这也是我们写装饰器的常规操作

def wrapper(func):
  def f(*args, **kwargs):
    print('在wrapper中执行func前')
    func(*args, **kwargs)
    print('在wrapper中执行func后')
  return f
@wrapper
def fengshan(str_obj):
  print(str_obj)
fengshan(str_obj='出风')

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
老生常谈进程线程协程那些事儿
Jul 24 Python
Python程序员面试题 你必须提前准备!
Jan 16 Python
PYTHON基础-时间日期处理小结
May 05 Python
python读取xlsx的方法
Dec 25 Python
元组列表字典(莫烦python基础)
Apr 03 Python
Django 查询数据库并返回页面的例子
Aug 12 Python
Pycharm+django2.2+python3.6+MySQL实现简单的考试报名系统
Sep 05 Python
keras模型可视化,层可视化及kernel可视化实例
Jan 24 Python
python中sys模块是做什么用的
Aug 16 Python
Python创建文件夹与文件的快捷方法
Dec 08 Python
python中pandas对多列进行分组统计的实现
Jun 18 Python
Python可视化神器pyecharts绘制地理图表
Jul 07 Python
Python字符串三种格式化输出
Sep 17 #Python
python判断变量是否为列表的方法
Sep 17 #Python
Django实现文章详情页面跳转代码实例
Sep 16 #Python
如何基于Django实现上下文章跳转
Sep 16 #Python
Python通过类的组合模拟街道红绿灯
Sep 16 #Python
python如何绘制疫情图
Sep 16 #Python
如何用Python绘制3D柱形图
Sep 16 #Python
You might like
PHP入门速成教程
2007/03/19 PHP
phpmyadmin里面导入sql语句格式的大量数据的方法
2010/06/05 PHP
PHP+MySQL高并发加锁事务处理问题解决方法
2018/04/30 PHP
asp.net+jquery滚动滚动条加载数据的下拉控件
2010/06/25 Javascript
jQuery EasyUI 的EasyLoader功能介绍
2010/09/12 Javascript
js实现jquery的offset()方法实例
2015/01/10 Javascript
有关suggest快速删除后仍然出现下拉列表的bug问题
2016/12/02 Javascript
angular实现form验证实例代码
2017/01/17 Javascript
详解Node.js实现301、302重定向服务
2017/04/07 Javascript
关于vue-resource报错450的解决方案
2017/07/24 Javascript
JavaScript实现计数器基础方法
2017/10/10 Javascript
Nodejs使用Mongodb存储与提供后端CRD服务详解
2018/09/04 NodeJs
详解vue2.0 资源文件assets和static的区别
2018/11/27 Javascript
详解Vue中watch的详细用法
2018/11/28 Javascript
小程序getLocation需要在app.json中声明permission字段
2019/04/04 Javascript
JavaScript函数式编程(Functional Programming)箭头函数(Arrow functions)用法分析
2019/05/22 Javascript
javascript面向对象创建对象的方式小结
2019/07/29 Javascript
vue基本使用--refs获取组件或元素的实例
2019/11/07 Javascript
JavaScript监听触摸事件代码实例
2019/12/30 Javascript
基于python时间处理方法(详解)
2017/08/14 Python
利用Python如何批量修改数据库执行Sql文件
2018/07/29 Python
PIL包中Image模块的convert()函数的具体使用
2020/02/26 Python
Python中如何添加自定义模块
2020/06/09 Python
Python Pandas list列表数据列拆分成多行的方法实现
2020/12/14 Python
pytorch下的unsqueeze和squeeze的用法说明
2021/02/06 Python
如何利用input事件来监听移动端的输入
2016/04/15 HTML / CSS
来自美国主售篮球鞋的零售商店:KICKSUSA
2017/11/28 全球购物
美国专业消费电子及摄影器材网站:B&H Photo Video
2019/12/18 全球购物
关于Assembly命名空间的三个面试题
2015/07/23 面试题
彩色的非洲教学反思
2014/02/18 职场文书
学校法制宣传月活动总结
2014/07/03 职场文书
公司领导九九重阳节发言稿2014
2014/09/25 职场文书
校园安全主题班会
2015/08/12 职场文书
2019事业单位个人工作总结范文
2019/08/26 职场文书
配置nginx负载均衡
2022/05/06 Servers
聊聊CSS粘性定位sticky案例解析
2022/06/01 HTML / CSS