Python 装饰器详解(下)

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532631.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xss-lab靶场通关writeup(1~6.......在更新)

level 2 : 标签被编码&#xff0c;利用属性完成弹窗 输入 发现没有弹窗 查看源代码&#xff1a; 发现&#xff1a; <>符号被编码 说明keybord参数进行了处理&#xff0c;那么只能从属性上进行恶意编码&#xff1a;先将属性的引号和标签闭合&#xff0c;用 // 将后面的…

PyTorch 分布式训练DDP 单机多卡快速上手

PyTorch 分布式训练DDP 单机多卡快速上手 本文旨在帮助新人快速上手最有效的 PyTorch 单机多卡训练&#xff0c;对于 PyTorch 分布式训练的理论介绍、多方案对比&#xff0c;本文不做详细介绍&#xff0c;有兴趣的读者可参考&#xff1a; [分布式训练] 单机多卡的正确打开方式…

Linux free 命令详解

Linux free 命令详解 free 命令用来查看系统中已用的和可用的内存。 命令选项及输出简介 关于各种命令的功能和命令选项&#xff0c;还是推荐英语比较好的同学直接看手册 RTFM&#xff1a;man free。这里简单总结一下一些重点&#xff1a; 功能及输出简介 free 命令显示系…

CTF web题 wp:

1.签到题 火狐F12查看源码&#xff0c;发现注释&#xff1a; 一次base64解码出flag 2.Encode 在这里插入图片描述 和第一题界面一样&#xff1f;&#xff1f; 轻车熟路f12&#xff1a; 发现编码&#xff1a; 格式看上去是base64&#xff0c;连续两次base64后&#xff0c;观…

【深度学习】深入理解Batch Normalization批归一化

【深度学习】深入理解Batch Normalization批归一化 转自&#xff1a;https://www.cnblogs.com/guoyaohua/p/8724433.html 这几天面试经常被问到BN层的原理&#xff0c;虽然回答上来了&#xff0c;但还是感觉答得不是很好&#xff0c;今天仔细研究了一下Batch Normalization的原…

ThinkPHP V5 漏洞利用

ThinkPHP 5漏洞简介 ThinkPHP官方2018年12月9日发布重要的安全更新&#xff0c;修复了一个严重的远程代码执行漏洞。该更新主要涉及一个安全更新&#xff0c;由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞&#xff0c;受影响的版本…

Vim 重复操作的宏录制

Vim 重复操作的宏录制 转自&#xff1a;https://www.cnblogs.com/ini_always/archive/2011/09/21/2184446.html 在编辑某个文件的时候&#xff0c;可能会出现需要对某种特定的操作进行许多次的情况&#xff0c;以编辑下面的文件为例&#xff1a; ; ;This is a sample config…

Vim 进阶1

Vim 进阶1 所有你觉得简单重复&#xff0c;可以自动化实现的操作&#xff0c;都是可以自动化实现的。 Vim光标移动拾遗 w&#xff1a;下一个单词的开头&#xff0c;e&#xff1a;下一个单词的结尾&#xff0c;b&#xff1a;上一个单词的开头&#xff0c; 0&#xff1a;行首…

攻防世界web题ics-06(爆破id值)

打开界面&#xff1a;嚯&#xff01;这花里胡哨 点来点去只有报表中心有回显&#xff1a; 发现url中id等于1&#xff0c;sql注入尝试无果&#xff0c; burp工具爆破id 对id的值进行爆破 burp报ERROR的话这是个bug&#xff0c;先点击Hex后点decimal手动刷新就可以使用 强行总…

crontab用法与实例

crontab用法与实例 本文基于 ubuntu 18.04 在Linux系统的实际使用中&#xff0c;可能会经常碰到让系统在某个特定时间执行某些任务的情况&#xff0c;比如定时采集服务器的状态信息、负载状况&#xff1b;定时执行某些任务/脚本来对远端进行数据采集等。这里将介绍下crontab的配…

手工sql注入常规总结

1.发现注入点 2.报数据库 先用单引号&#xff08;也尝试双引号&#xff09;闭合前面的语句&#xff0c;使注入的语句能够执行&#xff0c; 数字 0 :匹配字段&#xff0c;还有 11 12 等等都可以使用&#xff0c;有些网站会有过滤处理&#xff0c;建议采用 1%2b12 1%2b1>1 绕…

Systemd入门教程:命令篇

Systemd入门教程&#xff1a;命令篇 转自&#xff1a;http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 7日 Systemd 是 Linux 系统工具&#xff0c;用来启动守护进程&#xff0c;已成为大多数…

【CVE-2018-12613】phpmyadmin 4.8.1 远程文件包含漏洞复现

**环境&#xff1a;**http://62.234.56.138:8080/server_databases.php 官网下载phpmyadmin 4.8.1 源码&#xff1a;index.php文件中 函数含义&#xff1a; targer非空targer是否位字符串不能以index为开头&#xff0c;即过滤了index值不能出现在blacklist内&#xff0c;即…

Systemd 入门教程:实战篇

Systemd 入门教程&#xff1a;实战篇 转自&#xff1a;https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 8日 上一篇文章&#xff0c;我介绍了 Systemd 的主要命令&#xff0c;今天介绍如何使…

关于ubuntu自定义service服务时找不到/usr/lib/systemd/system目录的问题

关于ubuntu自定义service服务时找不到/usr/lib/systemd/system目录的问题 问题 我们知道在 systemd 取代了 init 而成为广大 Linux 系统中 PID 为1的守护进程之后&#xff0c;Linux 中的服务&#xff08;service&#xff09;主要有 systemd 命令组来实现。在大多数发行版 Lin…

攻防世界web2(逆向加密算法)

打开网页有如下代码&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$…

ctags 基本使用方法

ctags 基本使用方法 简介 ctags&#xff08;Generate tag files for source code&#xff09;是vim下方便代码阅读的工具。尽管ctags也可以支持其它编辑器&#xff0c;但是它正式支持的只有 Vim。并且 Vim 中已经默认安装了 ctags&#xff0c;它可以帮助程序员很容易地浏览源…

vimrc配置文件

vimrc配置文件 转自&#xff1a;https://www.ruanyifeng.com/blog/2018/09/vimrc.html Vim 是最重要的编辑器之一&#xff0c;主要有下面几个优点。 可以不使用鼠标&#xff0c;完全用键盘操作。系统资源占用小&#xff0c;打开大文件毫无压力。键盘命令变成肌肉记忆以后&am…

CTFHUB 《请求方式》 http请求,curl命令总结

打开网页&#xff1a; 思路一&#xff1a; 根据题目&#xff0c;应该是向网页发送get方式请求&#xff0c;但并没有具体规定要发送什么&#xff0c;尝试get发送参数后&#xff0c;都没有返回网页&#xff0c;emmm’…好像不是我想的那种套路 思路二&#xff1a; 网上找到思路…

Vim进阶2 map映射

Vim进阶2 map映射 简介 map是一个 vim 中的一些列映射命令&#xff0c;将常用的很长的命令映射到一个新的功能键上。map是Vim强大的一个重要原因&#xff0c;可以自定义各种快捷键&#xff0c;用起来自然得心应手。 map系列命令格式 格式 以 map 命令为例&#xff0c;它的…