通俗易懂了解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实现KNN邻近算法
Jan 28 Python
python抓取网页中链接的静态图片
Jan 29 Python
Python实现的视频播放器功能完整示例
Feb 01 Python
一篇文章读懂Python赋值与拷贝
Apr 19 Python
对python:threading.Thread类的使用方法详解
Jan 31 Python
Python3.7 新特性之dataclass装饰器
May 27 Python
Python日志处理模块logging用法解析
May 19 Python
TensorFlow中如何确定张量的形状实例
Jun 23 Python
Python Matplotlib简易教程(小白教程)
Jul 28 Python
python自动化测试三部曲之request+django实现接口测试
Oct 07 Python
解决tensorflow模型压缩的问题_踩坑无数,总算搞定
Mar 02 Python
python可视化之颜色映射详解
Sep 15 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
快速配置PHPMyAdmin方法
2008/06/05 PHP
PHP设计模式之命令模式的深入解析
2013/06/13 PHP
Laravel 5 框架入门(一)
2015/04/09 PHP
php传值赋值和传地址赋值用法实例分析
2015/06/20 PHP
Yii redis集合的基本使用教程
2020/06/14 PHP
关于JavaScript中var声明变量作用域的推断
2010/12/16 Javascript
jQuery版Tab标签切换
2011/03/16 Javascript
jquery中dom操作和事件的实例学习 仿yahoo邮箱登录框的提示效果
2011/11/30 Javascript
JQuery设置获取下拉菜单某个选项的值(比较全)
2014/08/05 Javascript
AngularJS基础学习笔记之表达式
2015/05/10 Javascript
js判断PC端与移动端跳转
2020/12/24 Javascript
jQuery插件FusionCharts绘制ScrollColumn2D图效果示例【附demo源码下载】
2017/03/22 jQuery
详解js正则表达式验证时间格式xxxx-xx-xx形式
2018/02/09 Javascript
JS中Promise函数then的奥秘探究
2018/07/30 Javascript
Vue自定义弹窗指令的实现代码
2018/08/13 Javascript
layer关闭弹出窗口触发表单提交问题的处理方法
2019/09/25 Javascript
js实现淘宝首页的banner栏效果
2019/11/26 Javascript
Node.js创建一个Express服务的方法详解
2020/01/06 Javascript
原生js实现自定义滚动条组件
2021/01/20 Javascript
python访问sqlserver示例
2014/02/10 Python
Python程序员开发中常犯的10个错误
2014/07/07 Python
Python中的__new__与__init__魔术方法理解笔记
2014/11/08 Python
对numpy中布尔型数组的处理方法详解
2018/04/17 Python
利用Python如何生成便签图片详解
2018/07/09 Python
Python使用统计函数绘制简单图形实例代码
2019/05/15 Python
python实现企业微信定时发送文本消息的示例代码
2020/11/24 Python
利用CSS3实现平移动画效果示例代码
2016/10/12 HTML / CSS
关于iframe跨域使用postMessage的实现
2019/10/29 HTML / CSS
Backcountry旗下的户外商品闪购网站:steep&cheap
2016/09/22 全球购物
为您搜罗全球潮流時尚品牌:HBX
2019/12/04 全球购物
什么情况下你必须要把一个类定义为abstract的
2013/01/06 面试题
军训感想500字
2014/02/20 职场文书
小学社团活动总结
2014/06/27 职场文书
党的群众路线教育实践活动通讯稿
2014/09/10 职场文书
2014保险公司个人工作总结
2014/12/09 职场文书
2015年大学班级工作总结
2015/04/28 职场文书