【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将博客内容html导出为Markdown格式

《Python将博客内容html导出为Markdown格式》Python将博客内容html导出为Markdown格式,通过博客url地址抓取文章,分析并提取出文章标题和内容,将内容构建成html,再转... 目录一、为什么要搞?二、准备如何搞?三、说搞咱就搞!抓取文章提取内容构建html转存markdown

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Python Websockets库的使用指南

《PythonWebsockets库的使用指南》pythonwebsockets库是一个用于创建WebSocket服务器和客户端的Python库,它提供了一种简单的方式来实现实时通信,支持异步和同步... 目录一、WebSocket 简介二、python 的 websockets 库安装三、完整代码示例1.

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.