Python 装饰器详解(下)

转自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主仅对其中 demo 实现中不适合python3 版本的语法进行修改,并微调了排版,本转载博客全部例程博主均已亲测可行。

Python 3.8.5

ubuntu 18.04

声明:此文章为,python装饰器详解——下篇,上一篇文章中,即详解装饰器——中篇 ,已经详细讲解了两大类装饰器,即函数装饰器、类装饰器的应用实例,并且分析了它们在运行的过程中的本质,给出了类装饰器的一般模板,本文将以实际例子为依托,讲解剩下的两个内容(闭包和装饰器的嵌套),其中,闭包是重点,包括闭包的诞生背景,闭包的定义、作用、与装饰器的关系与区别。该系列文章共分为 上、中、下 三篇。此为第三篇。

一、闭包诞生的背景——closure

1. 一个意想不到的窘境

很多的语言都存在闭包(closure),我们也常常听起这样的概念,但是你真的理解它了吗?东它的本质吗?在讲闭包之前,我打算从一个简单的情况说起。请先看一个例子:

func_list = []
for i in range(3):def myfunc(a):return i+afunc_list.append(myfunc)  #定义三个函数,将三个函数存放在一个列表中for f in func_list:           #调用列表中的三个函数print(f(1))

上面的运行结果是1 2 3 吗?但是真是的运行结果确实3 3 3。这是为什么?粗略的分析,第一个函数返回的应该是0+1,第二个返回的应该是1+1 ,第三个返回的应该是 2+1 啊,那为什么会出现这样的结果呢?从结果上分析,应该三个函数都是返回的 2+1,这是为什么呢?因为函数定义在循环内部,虽然每一次看起来好像 i 分别为 0、1、2,实际上因为函数是没有办法去保存这个变化的i 的,也就是说,i,是在函数外面发生变化的,函数里面的i会一直随着i的变化而变化,直到最终这个i不变化了,那函数里面的i是多少就是多少了。总结起来就一句话:

循环体内定义的函数是无法保存循环执行过程中的不停变化的外部变量的,即普通函数无法保存运行环境!还是不理解?

再看一个简单的例子:

a=100def myfunc(b):return a+bprint(myfunc(200))a=200
print(myfunc(200))

上面的代码大家都懂,运行结果为300 400。我们可以发现,因为函数内部有用到外面的a,所以函数运行的结果会随着这个a的变化而变化,直到外面的a不变了为止,否则光函数传递的参数是确定的还不够,还要取决于a。我们用两个比较通俗的层面去理解:

  1. 函数内部使用到了a,b,但是a却不是函数本身具备的财产,我虽然可以使用,但是我却不能决定它,a变化了,函数的结果就跟着变化了,直到a取最终的值,否则函数都是变化的。(你不确定,我就永远没有办法确定,你虽然就在我我身边,但是我却不能真正掌控你,这种感觉难道不难受吗?)

  2. 用书面语言说,函数没有办法保存它的运行环境,什么意思,在上面的两个例子里面,函数的运行环境都是这个模块(即py文件),也就是说,在这个运行环境里面的一切,函数都是没有办法做主的,函数能够做主只有他自身的局部作用域(包括形参)。

2. 窘境的解决办法

func_list = []
for i in range(3):def decorator(i):      #定义一个外层函数,这里之所以使用decorator,是为了后面与“装饰器进行比较def wrapper(a):    #定义一个内层函数,定义为wrapper是为了后面的比较return i + areturn wrapperfunc_list.append(decorator(i))  for f in func_list:print(f(1))

运行结果为 1 2 3 。关于为什么后面再详细讲解,这里先提供一种解决思路。

二、闭包的定义及应用

1. 闭包的定义

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。—— 维基百科

2. 闭包的作用

闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性(保存运行环境与变量状态

3. 闭包的特征

上面的描述还是不够精炼,有没有几个特别的特征,让人一眼就看出来它就是闭包呢?

  1. 必须要有函数的嵌套。而且外层函数必须返回内层函数,但是内层函数可以不返回值,也可以返回值;外层函数给内层函数提供了一个 “包装起来的运行环境”,在这个“包装的”运行环境里面,内层函数可以完全自己做主。这也是称之为闭包的原因了。

  2. 内层函数一定要用到外层函数中定义的变量。如果只满足了特征(1),也不算是闭包,一定要用到外层“包装函数”的变量,这些变量称之为 “自由变量”。

3. 闭包的代码解析

依然以上面的那么例子而言,我们提出了解决窘境的办法,那我们现在来解释这个解决办法到底做了什么工作。

func_list = []
for i in range(3):def decorator(i):      #定义一个外层函数,这里之所以使用decorator,是为了后面与“装饰器进行比较def wrapper(a):    #定义一个内层函数,定义为wrapper是为了后面的比较return i + areturn wrapperfunc_list.append(decorator(i))  for f in func_list:print(f(1))

这里列表中存出的就是三个包装函数decorator(1)decorator(2)decorator(3),其实相当于三个如下的定义:

def decorator(i=1):def wrapper(a):return i+a

因为这里wrapper的运行环境为decorator,不再是全局的环境,所以在wrapper的环境中,i 是固定的,不会再变化,故而当然能够自己做主了。

三、闭包的细节

首先明确闭包的两个核心特征:函数嵌套 和 自由变量

其次明确闭包的两个核心功能:保存函数的运行环境状态 和 保存闭包环境内的局部变量

1. 闭包的细节实现

看一个简单的闭包的例子,为了与前面的系列文章(中篇)的装饰器进行比较,这里也采用中篇中的案例,我要为一个两数相加的运算加密:

def decorator(c):  #外层函数,产生包装环境——即闭包d=200           #c d 都是包装环境中的局部变量——即自由变量def wrapper(a,b):  #内层函数return (a+b)*c/dreturn wrapperwrapper=decorator(150)
print(wrapper(100,300))

运行结果为:300.0

  1. **为什么说它保存了函数的运行环境?**这里针对函数是内层函数即wrapper,它的运行环境是decorator提供的,也就是说decorator的环境是保存的,什么意思呢,其实就是通过一句话,

    wrapper=decorator(150)
    

    也就是说,这里wrapper运行所依赖的 c 就固定是150了,d 就固定是200了,不会再改变,无论我再给wrapper 传递什么参数,cd 是不会在变化的。当然如果我重新再执行一次wrapper=decorator(250),相当于是又创建了一个新的包装环境,这里的 c 就是250了。

  2. **为什么说它能够保存闭包函数内的局部变量?**众所周知,函数的局部变量会随着函数的调用结束而销毁,那么为什么局部变量能够保存呢?这里所说的局部变量指的是闭包函数的局部变量,即上面的 cd。也就是说,我这里的 cd 是保存着的,即使我已经执行wrapper(100,300)执行完毕。

2. 自由变量的查看

我们说闭包函数的局部变量是保存着的,那如何查看呢?我们可以通过内层函数的一个属性__closure__查看。

print(wrapper.__closure__)
print(wrapper.__closure__[0].cell_contents)
print(wrapper.__closure__[1].cell_contents)

结果如下:

(<cell at 0x7f2c886802b0: int object at 0x5558ee7e6fe0>, <cell at 0x7f2c88680370: int object at 0x5558ee7e7620>)
150
200

可以看到 __closure__ 属性返回一个元组,而150和200则分别对应自由变量 cd

总结:内层函数的__closure__属性返回一个元组;通过 wrapper.__closure__[i].cell_contents 查看第几个自由变量的值

注意:如果闭包函数没有返回wrapper,即外层函数没有返回内层函数,此时内层函数是没有__closure__属性的。

总结:现在可以体会为什么说闭包保存局部变量了吧,这里的c d 作为局部变量,在函数调用结束后还能够查看到它的值,这还不是保存,那什么是保存呢?

3. 闭包的一般模板

def decorator(*arg,**kargs):  #外层函数,产生包装环境——即闭包#自由变量区域                 # 包含形参,都是包装环境中的局部变量——即自由变量def wrapper(a,b):  #内层函数return (a+b)*c/dreturn wrapperwrapper=decorator(150)      #创建唯一的闭包环境
wrapper(100,300)            #内层函数的调用

四、闭包与装饰器的比较

1. 相同点

  1. 都是函数的嵌套,分为外层函数和内层函数,而且外层函数要返回内层函数

  2. 代码的实现逻辑大同小异

  3. 二者都可以实现增加额外功能的目的——比如上面的“加法加密运算”

2. 不同点

  1. 外层函数不同,装饰器的外层函数称之为decorator,闭包的外层函数称之为闭包函数closure

  2. 外层函数的目的不同,装饰器的外层函数主要是提供函数形参function,闭包的形参主要目的是提供自由变量。

  3. 二者的特征不一样。装饰器的外层函数可以不提供自由变量,但是闭包的的外层函数一定要提供自由变量,因为如果不提供自由变量,必报的存在就毫无意义了,即内层函数所依赖的变量却在闭包中根本没有,那还要闭包干什么?

  4. 二者的主要目的不同。装饰器的目的:代码重用+额外功能。闭包的主要目的:保存函数的运行环境+保存闭包的局部变量。虽然二者可以有一些交集。

  5. 闭包和装饰器本质上还是不一样的,但是从形式上来说,大致可以认为闭包是装饰器的子集。记住:仅仅是从形式上哦!

3. 如何理解“闭包”与“装饰器”的本质不一样,但是形式类似?

关于形式类似,这里就不说了,参见前面的两篇文章和这篇文章里面的模板即可,发现他们长得很像。

为什么说本质不一样?

  1. 因为对与装饰器而言,我必须要给外层函数 decorator 传递一个基本参数 function,只有这样,我才可以写成function=decorator(function)或者是 @decorator 的形式,如果没有这个参数,会显示以下错误:

     decorator() takes 0 positional arguments but 1 was given
    

    decorator 我必须要定义一个 function 参数,否则就会显示定义没有参数,但给了它一个参数这种错误,因为 function=decorator(function) 或者是 @decorator 这就相当于给了他一个参数。

    不仅如此,装饰器会改变函数 function 本身的 __name__ 属性,参见前文。

  2. 但是对于闭包,外层函数就没有这些要求,也不是一定要定义一个 function 参数,甚至我也可以不定义参数。至于两者的本质区别,学懂了的小伙伴应该可以自己好好体会了。

五、装饰器的嵌套

关于装饰器的多层嵌套,理解起来相对于比较复杂,本文先做一个预告,将在系列文章的下一篇,也就是第四篇进行深入详解,有需要的可以关注一下。
传送门:https://blog.csdn.net/qq_27825451/article/details/102457152。

Python 装饰器详解(下)相关推荐

  1. Python 装饰器详解(中)

    Python 装饰器详解(中) 转自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主仅对其中 demo 实现中不适合pyth ...

  2. Python 装饰器详解(上)

    Python 装饰器详解(上) 转自:https://blog.csdn.net/qq_27825451/article/details/84396970,博主仅对其中 demo 实现中不适合pyth ...

  3. Python装饰器详解,详细介绍它的应用场景

    装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 ...

  4. python装饰器详解51-python装饰器使用实例详解

    这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...

  5. python装饰器详解-python装饰器使用实例详解

    这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...

  6. python装饰器详解-Python装饰器基础概念与用法详解

    本文实例讲述了Python装饰器基础概念与用法.分享给大家供大家参考,具体如下: 装饰器基础 前面快速介绍了装饰器的语法,在这里,我们将深入装饰器内部工作机制,更详细更系统地介绍装饰器的内容,并学习自 ...

  7. python装饰器作用-Python装饰器详解

    1.闭包函数 在看装饰器之前,我们先来搞清楚什么是闭包函数.python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有. 这样我们就可以理解在函数内创 ...

  8. python类装饰器详解-Python 装饰器详解

    开放封闭原则: 开放对扩展 封闭修改源代码 改变了人家调用方式 装饰器结构 """ 默认结构为三层!!!每层返回下一层内存地址就可以进行执行函数, 传参:语法糖中的传参可 ...

  9. 这是我见过最全面的Python装饰器详解!没有学不会这种说法

    python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,才有点点开始明白了. 学习python中有什么不懂的地方,小编这 ...

最新文章

  1. 保障数据安全,强调科技向善,旷视发布《人工智能应用准则》
  2. PAT甲级1104 Sum of Number Segments:[C++题解]数段之和,测试点2爆double,请用long double!!!
  3. Managed keyedState中的ValueStateDescriptor与MapStateDescriptor
  4. vision软件_Roboguide软件:高速拾取仿真工作站相机与工具添加与配置
  5. C 温故知新 之 指针:函数指针变量、指针型函数
  6. bigru参数计算_[数据挖掘]华中科技大学 李黎 周达明:基于CNN-BiGRU模型的操作票自动化校验方法...
  7. OpenCV-均值滤波cv::blur
  8. 解决TextView上方空白
  9. c串口一直读到缓存数据_STM32CubeMX之串口不定长数据接收(接收中断+空闲中断)...
  10. 检测文章相似度的方法?文章原创度检测工具免费
  11. Hbase------刘飞
  12. html5查询通配符,通配符有哪些?
  13. 马斯克的“星链”会不会威胁中国太空安全?肯定会!
  14. 宝塔linux 搭建rtmp+ffmpeg转流直播服务器
  15. 计算机仿真和vr的区别,扫盲科普:今天才知道VR和AR的区别
  16. 项目 | Java+PhantomJs爬虫实战——半次元 下载高清原图
  17. 霍营派出所办理居住证
  18. 如何快速的转发别人的CSDN博客(转载)
  19. 错误: tensorflow:Early stopping conditioned on metric `val_accuracy` which is not available
  20. Linux SA--HugePage,HPET

热门文章

  1. 浩鲸新智能解决方案工程师面试_【华为解决方案工程师面试题目|面试经验】-看准网...
  2. mysql5.7是测试版本吗_mysql免安装版本测试(mysql-5.7.18-winx64)
  3. linux账户初始化文件,Linux启动初始化配置文件浅析
  4. 初识C++之运算符重载
  5. php angular使用,如何使用angular.js PHP从mysql显示数据?
  6. android开机渲染视频太久,Android过度渲染优化解决方案
  7. 的表格点击全选_“逼死”强迫症的杂乱表格,原来3秒就能整理好!(必学)...
  8. Android打开谷歌应用,谷歌确认 Android 12 新增剪贴板访问提醒,将在 Beta 2 上线
  9. Java可移动性不强_java地位无可撼动的原因
  10. java 缩略图 库_Thumbnailator:一个高质量Java缩略图开发库