【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

相关文章

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

python 字典d[k]中key不存在的解决方案

《python字典d[k]中key不存在的解决方案》本文主要介绍了在Python中处理字典键不存在时获取默认值的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录defaultdict:处理找不到的键的一个选择特殊方法__missing__有时候为了方便起见,

使用Python绘制可爱的招财猫

《使用Python绘制可爱的招财猫》招财猫,也被称为“幸运猫”,是一种象征财富和好运的吉祥物,经常出现在亚洲文化的商店、餐厅和家庭中,今天,我将带你用Python和matplotlib库从零开始绘制一... 目录1. 为什么选择用 python 绘制?2. 绘图的基本概念3. 实现代码解析3.1 设置绘图画

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

Python如何实现 HTTP echo 服务器

《Python如何实现HTTPecho服务器》本文介绍了如何使用Python实现一个简单的HTTPecho服务器,该服务器支持GET和POST请求,并返回JSON格式的响应,GET请求返回请求路... 一个用来做测试的简单的 HTTP echo 服务器。from http.server import HT