通俗易懂了解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 相关文章推荐
Python正则表达式的使用范例详解
Aug 08 Python
Python获取电脑硬件信息及状态的实现方法
Aug 29 Python
使用Python的Flask框架实现视频的流媒体传输
Mar 31 Python
Python随机生成数模块random使用实例
Apr 13 Python
用Python脚本来删除指定容量以上的文件的教程
May 04 Python
Python中is与==判断的区别
Mar 28 Python
python 使用 requests 模块发送http请求 的方法
Dec 09 Python
python获取微信企业号打卡数据并生成windows计划任务
Apr 30 Python
python3实现将json对象存入Redis以及数据的导入导出
Jul 16 Python
Python机器学习三大件之一numpy
May 10 Python
Pytorch 实现变量类型转换
May 17 Python
Python使用pyecharts控件绘制图表
Jun 05 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开发中的安全防范知识详解
2013/06/06 PHP
php连接Access数据库错误及解决方法
2013/06/20 PHP
laravel 根据不同组织加载不同视图的实现
2019/10/14 PHP
js动态加载以及确定加载完成的代码
2011/07/31 Javascript
html中使用javascript调用本地程序(exe、doc等)实现代码
2013/04/26 Javascript
禁止IE用右键的JS代码
2013/12/30 Javascript
使用javascript做的一个随机点名程序
2014/02/13 Javascript
jQuery中阻止冒泡事件的方法介绍
2014/04/12 Javascript
node.js中的querystring.parse方法使用说明
2014/12/10 Javascript
举例简介AngularJS的内部语言环境
2015/06/17 Javascript
浅谈javascript函数式编程
2015/09/06 Javascript
JS实现的页面自定义滚动条效果
2015/10/26 Javascript
JavaScript获取当前运行脚本文件所在目录的方法
2016/02/03 Javascript
js实现页面刷新滚动条位置不变
2016/11/27 Javascript
vue+vuex+axios+echarts画一个动态更新的中国地图的方法
2017/12/19 Javascript
10分钟上手vue-cli 3.0 入门介绍
2018/04/04 Javascript
浅谈vue引用静态资源需要注意的事项
2018/09/28 Javascript
详解微信小程序之一键复制到剪切板
2019/04/24 Javascript
jquery向后台提交数组的代码分析
2020/02/20 jQuery
[02:27]2018DOTA2亚洲邀请赛趣味视频之钓鱼大赛 谁是垂钓冠军?
2018/04/05 DOTA
用pywin32实现windows模拟鼠标及键盘动作
2014/04/22 Python
Python获取Linux系统下的本机IP地址代码分享
2014/11/07 Python
简单介绍Python中用于求最小值的min()方法
2015/05/15 Python
Pycharm学习教程(5) Python快捷键相关设置
2017/05/03 Python
Python内建函数之raw_input()与input()代码解析
2017/10/26 Python
关于django 数据库迁移(migrate)应该知道的一些事
2018/05/27 Python
对python同一个文件夹里面不同.py文件的交叉引用方法详解
2018/12/15 Python
python实现WebSocket服务端过程解析
2019/10/18 Python
基于Python实现ComicReaper漫画自动爬取脚本过程解析
2019/11/11 Python
flask框架配置mysql数据库操作详解
2019/11/29 Python
pytorch实现建立自己的数据集(以mnist为例)
2020/01/18 Python
python opencv图像处理(素描、怀旧、光照、流年、滤镜 原理及实现)
2020/12/10 Python
墨西哥网上购物:Linio墨西哥
2016/10/20 全球购物
英国健身仓库:Bodybuilding Warehouse
2019/03/06 全球购物
初中地理教学反思
2016/02/19 职场文书
漫画「古见同学有交流障碍症」第25卷封面公开
2022/03/21 日漫