【python小课堂专栏】python小课堂26 - 进阶必修之闭包(一)

2023-10-24 11:38

本文主要是介绍【python小课堂专栏】python小课堂26 - 进阶必修之闭包(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

python小课堂26 - 进阶必修之闭包(一)

前言

时光飞逝,直至今日,2019年的第一个月都要过完了!从2018年10月份决心开始写python小知识,已经过了3个月了,写到现在基本上占总进度的一半了吧!

从本章起,开始进行python进阶篇的知识分享,python小课堂0-25皆为基础知识,其中有两篇是结合基础讲解实战,分别是暴力破解和图片定位,链接如下:

python小课堂20 - 5分钟教你用图片定位具体地址!
python小课堂17 - 30行代码破解加密ZIP文件

相信学到现在,这两篇实战的原理以及代码你一定可以看懂了!好了,废话不多说了,进入今天的正题吧,python进阶篇-闭包!

再谈函数

讲真的,闭包从概念是非常难讲明白的一个词。所以,先来搞清楚函数在Python里是怎样的存在,深入了解了函数的概念,会为学习闭包打下基础。闭包与函数有着密不可分的关系。

通常,在一般的编程语言中,函数只是一段可执行的逻辑代码,并不是所谓的对象。比如在Java中,如下代码,打印输出一句话:

/**
* 函数描述:说一句话
* public : 共有方法
* void   :没有返回类型
* @param words 参数,传入的话语
*/
public static void say(String words) {System.out.println(words);
}
public static void main(String[] args) {say("你好");
}

在这里插入图片描述

像Java这种有明确类型定义声明的语言,如果没有设置返回类型,在调用函数时,则不能用任何变量去接受,否则编译时就会报错。(只是作对比的例子,非专业人员忽略即可)。

再来看下Python中呢,若我们像打印一句话,也需要定义一个函数,然后调用即可,同时,我们可以通过任何变量来将此函数进行赋值操作,如下:

# 打印一句话
def say(words):print(words)say('你好')
a = say
print(type(a))

在这里插入图片描述

结果显示,在Python中,是可以用函数不加小括号的形式将其赋值给任何变量,并且其自身也可以作为函数的参数进行传递,不仅如此,函数也可以用此种方式将其自身作为返回结果进行返回,通过上面的代码可以看到,say赋值给变量a,打印a的类型,得到class function,说明它是一个。之前的小课堂中提及过,在Python中一切皆为对象,函数也不例外!

可能有人会说,你在Java中不是这么写的啊,你也像在Python中那样调用试下,看看结果呗,于是有了下面的图:

在这里插入图片描述

可以看到,当在Java中写say不加小括号时,编译器已经报错了,提示找不到say的标识符(会Java的人都知道这么写是没有意义的)。这也是Java与Python作为编程语言的不同之处,一个为编译型语言,一个为解释型语言。

正因为Python有了这么一个特性,所以才会很好地支持接下来要介绍的闭包!

什么是闭包?

在解释概念之前,先来看个自带场景的小例子吧!不知道大家还记不记得小学(是小学还是初中来着,忘记了…默认小学吧!)学过的一个数学公式,如何去求圆的面积?记忆好的一定记得:S=π*r^2r为圆的半径,π为3.1415926…

现在的场景是,需要定义一个求圆形面积的函数,同时,在这个函数的外部还要包裹着一层求圆形面积之前提前做准备的外层函数,这外层函数的目的是你可以在真正求圆形面积前演算一些内容(假设大家都是刚学这个公式的小学生哟!),写法分解成以下步骤:

1. 定义两个函数,并且在调用内部计算圆形面积的函数
def circular_area_pre():def circular_area():print('This is circular_area function!')

其中 circular_area_pre 是计算圆形面积前的准备函数,circular_area 是计算圆形面积的函数。此时若想在外面直接调用 circular_area 如何做呢?(不要感到这种写法奇怪,Python中是可以进行函数嵌套的!)尝试下自己手动调用!如下图:

在这里插入图片描述

当尝试在外层直接进行调用时,可以看到,已经报错了!如何正确调用呢?在上面的标题「再谈函数」中说过,函数可以通过“对象”的写法将其自身作为结果进行返回!(忘记的往上翻翻,找找看!)所以改下写法如下:

def circular_area_pre():def circular_area():print('This is circular_area function!')return circular_area()c = circular_area_pre()
c()

通过调用外层的计算准备函数 circular_area_pre ,函数返回接受到的内部计算圆形面积函数 circular_area 作为变量c,以函数的形式调用变量c(也就是在c后面加上小括号进行函数调用),执行即可!而此时的变量c实际上是一个函数(这点在后面的步骤中会进行验证,先记住。)!执行一下,咦???发现报错了,因为多了个小括号:

在这里插入图片描述

去掉后成功,所以一定要注意小括号的问题!!如下图 :

在这里插入图片描述

2. 在第一步的基础上,将其补充完整,套入公式
def circular_area_pre():""" 省略了一些演算步骤的代码,毕竟假设嘛 """pai = 3.14def circular_area(r):s = pai * r ** 2return sreturn circular_areac = circular_area_pre()
print(c(10))

解释下这段代码的含义,将pai(π)定义为3.14,放在作为计算圆形面积之前的函数 circular_area_pre 中,而求面积的公式则写在 circular_area 函数中,将面积变量s作为内部函数返回,同时,最外层的 circular_area_pre 函数返回 circular_area 函数作为结果。想要计算出圆形的面积,在外层就需要先调用 circular_area_pre 准备函数,在调用其返回结果变量c,打印 c(10),可以看到如下图结果:

在这里插入图片描述

打印结果输出的是314.0,实际上就是将10传入到了 circular_area 函数中,而其中的pai引用的是在 circular_area_pre 局部里定义的pai,值为3.14 。验证下步骤1说的,看下变量c的类型,以及直接打印c会出现什么样子的结果:
在这里插入图片描述

现在可以看到,其实变量c就是一个类,而它的类型是function.

3. 关于 pai 的定义位置

抛开外层的准备函数,假设现在只有计算圆形的函数,假设阿基米德突然复活了,把这个pai推算成了其它数字!姑且定义为10吧,如下:

pai = 3.14def circular_area(r):s = pai * r ** 2return s
pai = 10
print(circular_area(10))

在这里插入图片描述

输出结果为1000,记住这个值,咱们继续往下看!

4. 如果此时在外部修改了pai的值,打印结果如何?

回到双层函数的示例来,在 circular_area 函数中,pai是没有被定义的,所以它会向上一层寻找,于是找到了 circular_area_pre 函数中定义的pai,所以计算的时候值就会取为3.14,假设现在依然在最外层函数的外面修改pai的值,继续改为10,那么代码的结果会是如何呢?

在这里插入图片描述

来看下:

def circular_area_pre():""" 省略了一些演算步骤的代码,毕竟假设嘛 """pai = 3.14def circular_area(r):s = pai * r ** 2return sreturn circular_areapai = 10
c = circular_area_pre()
print(c(10))

在调用准备函数之前,将pai修改为10!猜猜看,结果会打印出什么呢?(先不要看结果,自己思考一下!)结果如下:

在这里插入图片描述

没错,你没有看错,依然是314.0,这个结果与没加入pai = 10 的代码得到的结果是一样的!为什么不是1000呢???请继续往后看!

5. 闭包的概念

啰里啰嗦的举例了这么多,到底跟今天要说的闭包有什么关系呢!各位看官,莫急,下面就是重头戏了,只要你耐心的把上面的示例都看懂,保证你看完接下来的这段概念性文字一目了之!

在上面的函数 circular_area 与 pai = 3.14 形成了闭包!通俗的说就是把内部函数 circular_area 与 pai = 3.14 这个环境变量包含在了一起,做了一个封闭,所以外界想要去改变 pai 这个变量是改变不了的!

需要注意的是,所谓的环境变量,一定要定义在内部函数的外部,就像 pai 一样,且不能是全局变量

闭包的概念:闭包 = 函数 + 环境变量!

6. Python函数作用域

Python变量的作用域一共有4种,分别是:

  • L (Local) 局部作用域

  • E (Enclosing) 闭包函数外的函数中

  • G (Global) 全局作用域

  • B (Built-in) 内建作用域

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

通过这段作用域的概念,可以得知,为什么在求面积的函数外部去修改pai的值,最终取到的还是原来的3.14,Python的变量查询是一个“由内到外”的过程,一旦找到,则取最内部的变量进行使用。

如何查看一个函数是否闭包

闭包是可以通过python内置函数检测出来的,如下:

def circular_area_pre():""" 省略了一些演算步骤的代码,毕竟假设嘛 """pai = 3.14def circular_area(r):s = pai * r ** 2return sreturn circular_areapai = 10
c = circular_area_pre()
print(c.__closure__)
print(c.__closure__[0].cell_contents)

在这里插入图片描述

通过调用 closure 内置方法可以查看到两个内存地址,结果返回cell就是闭包,None 则不是闭包,可以看出来其实这是一个元组类型,使用[0].cell_contents可以得到闭合数值,也就闭包所需要的环境变量

小题,请你判断下面这段代码属于闭包吗?如下:

def circular_area_pre():""" 省略了一些演算步骤的代码,毕竟假设嘛 """def circular_area(r):pai = 3.14s = pai * r ** 2return sreturn circular_areac = circular_area_pre()
print(c.__closure__)
print(c.__closure__[0].cell_contents)

答案:

在这里插入图片描述

只把pai 进行了换位,导致现在的写法并不是闭包,调用Python内置方法来查看,得到的是None,没获取到闭包时产生的cell!

总结

说真的,本章的闭包要想讲明白真的挺难得,这篇文章大概是花了一星期的时间去写的,基本上基础的概念介绍的差不多了,但是要想懂闭包,还是得看懂示例才行,记住闭包就是函数和环境变量封闭组成的!外界想改环境变量?没门!

后续还有一篇闭包二,会讲述闭包的使用场景,究竟什么时候使用闭包才是合适的。敬请期待…

至此完!


有想交流沟通python相关知识的同学,欢迎关注公号: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019012912401890.png)

这篇关于【python小课堂专栏】python小课堂26 - 进阶必修之闭包(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/274889

相关文章

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

Python 字符串占位

在Python中,可以使用字符串的格式化方法来实现字符串的占位。常见的方法有百分号操作符 % 以及 str.format() 方法 百分号操作符 % name = "张三"age = 20message = "我叫%s,今年%d岁。" % (name, age)print(message) # 我叫张三,今年20岁。 str.format() 方法 name = "张三"age

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

python 喷泉码

因为要完成毕业设计,毕业设计做的是数据分发与传输的东西。在网络中数据容易丢失,所以我用fountain code做所发送数据包的数据恢复。fountain code属于有限域编码的一部分,有很广泛的应用。 我们日常生活中使用的二维码,就用到foutain code做数据恢复。你遮住二维码的四分之一,用手机的相机也照样能识别。你遮住的四分之一就相当于丢失的数据包。 为了实现并理解foutain

python 点滴学

1 python 里面tuple是无法改变的 tuple = (1,),计算tuple里面只有一个元素,也要加上逗号 2  1 毕业论文改 2 leetcode第一题做出来

Python爬虫-贝壳新房

前言 本文是该专栏的第32篇,后面会持续分享python爬虫干货知识,记得关注。 本文以某房网为例,如下图所示,采集对应城市的新房房源数据。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。接下来,跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地址:aHR0cHM6Ly93aC5mYW5nLmtlLmNvbS9sb3VwYW4v 目标:采集对应城市的