Python闭包|你应该知道的常见用例(上)

引言
在 Python 编程语言中,闭包通常指的是一个嵌套函数,即在一个函数内部定义的另一个函数。这个嵌套的函数能够访问并保留其外部函数作用域中的变量。这种结构就构成了一个闭包。
闭包在函数式编程语言中非常普遍。在 Python 中,闭包特别有用,因为它使得你可以创建基于函数的装饰器,这是一种非常强大的功能。
通过本教程[1],你将:
-
了解闭包的概念以及它们在 Python 中的运作方式 -
掌握闭包的典型应用场景 -
探索闭包的替代方法 为了更好地理解本教程,你需要对 Python 的一些基本概念有所了解,比如函数、嵌套函数、装饰器、类和可调用对象。
了解 Python 中的闭包
闭包是一种函数,它能够保持对其词法环境的访问,即使该函数在该环境之外被执行。当外部函数返回内部函数时,你将获得一个具有扩展作用域的函数对象。
换言之,闭包是能够捕获它们所处环境(即封闭作用域)中定义的对象的函数,这使得你可以在函数体内使用这些对象。这个特性让你在需要在连续调用之间保持状态信息时,可以利用闭包。
在那些以函数式编程为主的编程语言中,闭包是一种常见的特性,而 Python 也支持闭包,作为其众多特性之一。
在 Python 中,闭包是你在另一个函数内部定义并返回的函数。这个内部函数能够保留在它定义之前非局部作用域中定义的对象。
为了更深入地理解 Python 中的闭包,你首先需要了解内部函数,因为闭包本质上也是内部函数的一种。
内部函数
在 Python 中,内部函数是指在另一个函数内部定义的函数。这类函数可以访问和修改它们所处外部函数(即非局部作用域)中的变量名。
这里有一个简单的例子:
>>> def outer_func():
... name = "Pythonista"
... def inner_func():
... print(f"Hello, {name}!")
... inner_func()
...
>>> outer_func()
Hello, Pythonista!
>>> greeter = outer_func()
>>> print(greeter)
None
在这个示例中,你在全局范围内定义了 outer_func()
函数。在这个函数内部,你声明了一个名为 local
的局部变量。接着,你定义了另一个名为 inner_func()
的函数。由于这个函数定义在 outer_func()
函数体内,因此它是一个嵌套函数。最终,你调用了这个嵌套函数,它使用了在外部函数中定义的 name
变量。
当你执行 outer_func()
函数时,inner_func()
函数将 name
变量的值嵌入到问候语字符串中,并将其显示在你的屏幕上。
在上述示例中,你定义了一个能够访问封闭作用域中变量的嵌套函数。但是,当你调用外部函数时,你并没有获得对嵌套函数的引用。嵌套函数和局部变量在外部函数之外是不可见的。
在接下来的部分,你将学习如何将嵌套函数转变为闭包,这样你就可以访问嵌套函数和它保留的变量了。
函数闭包
并非所有的嵌套函数都是闭包,但所有的闭包都是嵌套函数。要将一个嵌套函数转变为闭包,你需要从外部函数返回这个嵌套函数的对象。这听起来可能有些拗口,但以下是如何让 outer_func()
函数返回一个闭包对象的方法:
>>> def outer_func():
... name = "Pythonista"
... def inner_func():
... print(f"Hello, {name}!")
... return inner_func
...
>>> outer_func()
<function outer_func.<locals>.inner_func at 0x1066d16c0>
>>> greeter = outer_func()
>>> greeter()
Hello, Pythonista!
在 outer_func()
函数的这个新版本中,你返回的是 inner_func
函数对象本身,而不是执行它。这样,当你执行 outer_func()
函数时,你将得到一个闭包对象,而不是一条问候信息。这个闭包对象能够记住 name
变量的值,即使 outer_func()
函数执行完毕后,你依然可以通过闭包访问这个值。这就是为什么当你调用 greeter()
函数时能够看到问候信息的原因。
-
创建一个 Python 闭包需要以下元素:
-
一个外部或封闭函数:这个函数包含了另一个函数,通常称为内部函数。外部函数可以接收参数,并定义一些内部函数可以访问和修改的变量。 -
仅在外部函数中有效的局部变量:这些变量定义在外部函数的作用域内。Python 会保留这些变量,使得即使在外部函数执行完毕后,你仍然可以在闭包中使用它们。 -
一个内部或嵌套函数:这个函数定义在外部函数内部,它能够访问并修改外部函数中的变量,哪怕外部函数已经执行完毕。
在这个部分的例子中,你有一个外部函数、一个局部变量(name
)和一个内部函数。要得到一个闭包对象,最后一步是从外部函数返回内部函数对象。
另外值得一提的是,你也可以使用 lambda 表达式来创建闭包:
>>> def outer_func():
... name = "Pythonista"
... return lambda: print(f"Hello, {name}!")
...
>>> greeter = outer_func()
>>> greeter()
Hello, Pythonista!
在 outer_func()
函数的这个修改版中,你通过使用 lambda 函数来创建闭包,其功能与原始版本相同。
捕获的变量
如你所了解的,闭包会保留其封闭作用域内的变量。以下是一个简单的示例:
>>> def outer_func(outer_arg):
... local_var = "Outer local variable"
... def closure():
... print(outer_arg)
... print(local_var)
... print(another_local_var)
... another_local_var = "Another outer local variable"
... return closure
...
>>> closure = outer_func("Outer argument")
>>> closure()
Outer argument
Outer local variable
Another outer local variable
在这个示例中,当你执行 outer_func()
函数时,outer_arg
、local_var
和 another_local_var
这些变量都会被绑定到闭包上,哪怕它们原来所在的环境已经不存在了。不过,closure()
函数能够访问这些变量,因为它们现在成为了闭包的一部分。这就是为什么我们说闭包是一个拥有扩展作用域的函数。
闭包同样可以修改这些变量的值,这会导致两种不同的情况:变量可能指向一个不可变对象或者一个可变对象。
如果你想修改指向不可变对象的变量的值,你需要使用 nonlocal
声明。以下是一个示例:
>>> def make_counter():
... count = 0
... def counter():
... nonlocal count
... count += 1
... return count
... return counter
...
>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
>>> counter()
3
在这个示例中,count
变量保存了一个整数值的引用,而整数值是不可变的。如果你想更新 count
的值,就需要使用 nonlocal
声明,这告诉 Python 你希望在非局部作用域中重用这个变量。
当你的变量指向一个可变对象时,你可以直接修改这个变量的值:
>>> def make_appender():
... items = []
... def appender(new_item):
... items.append(new_item)
... return items
... return appender
...
>>> appender = make_appender()
>>> appender("First item")
['First item']
>>> appender("Second item")
['First item', 'Second item']
>>> appender("Third item")
['First item', 'Second item', 'Third item']
在这个示例中,变量 items
指向一个列表对象,列表是可变的。因此,你无需使用 nonlocal
关键字,可以直接在原地修改这个列表。
利用闭包保持状态
在实际应用中,你可以在多种不同场合使用 Python 闭包。在本节中,你将了解如何利用闭包来创建工厂函数,跨函数调用保持状态,以及实现回调机制,这将使你的代码更加动态、灵活和高效。
创建工厂函数
你可以编写一些函数来生成带有初始配置或参数的闭包。这在你想要创建多个具有不同设置的相似函数时非常有用。
例如,假设你需要计算不同度数和结果精度的数值根。在这种情况下,你可以编写一个工厂函数,该函数返回预设了度数和精度的闭包,如下所示:
>>> def make_root_calculator(root_degree, precision=2):
... def root_calculator(number):
... return round(pow(number, 1 / root_degree), precision)
... return root_calculator
...
>>> square_root = make_root_calculator(2, 4)
>>> square_root(42)
6.4807
>>> cubic_root = make_root_calculator(3)
>>> cubic_root(42)
3.48
make_root_calculator()
是一个工厂函数,用于生成计算不同数值根的函数。在这个函数中,你传入根的度数和期望的精度作为配置参数。
接着,你定义了一个内部函数,该函数接受一个数字作为输入,并计算出具有所需精度的特定根。最终,你返回这个内部函数,从而创建了一个闭包。
你可以利用这个函数来创建闭包,以便计算不同度数的数值根,例如平方根和立方根。需要注意的是,你还可以调整计算结果的精度。
构建有状态的函数
闭包可以用来在函数调用之间保持状态,这些函数被称为有状态函数,而闭包是实现它们的一种方法。
例如,假设你想编写一个函数,它从数据流中连续获取数值,并计算它们的累积平均值。在函数的多次调用之间,必须保留之前传递的值。在这种情况下,你可以使用如下所示的函数:
>>> def cumulative_average():
... data = []
... def average(value):
... data.append(value)
... return sum(data) / len(data)
... return average
...
>>> stream_average = cumulative_average()
>>> stream_average(12)
12.0
>>> stream_average(13)
12.5
>>> stream_average(11)
12.0
>>> stream_average(10)
11.5
在 cumulative_average()
函数中,data
这个局部变量使得你能够在连续调用这个函数返回的闭包对象时保持状态。
然后,你创建了一个名为 stream_average()
的闭包,并用不同的数值来调用它。注意这个闭包是如何记住之前传入的值,并通过累加新提供的价值来计算平均值的。
提供回调函数
闭包在事件驱动编程中非常常见,当你需要创建携带额外上下文或状态信息的回调函数时就会用到。图形用户界面(GUI)编程是这类回调函数的一个典型应用场景。
例如,假设你想使用 Tkinter(Python 的标准 GUI 编程库)来创建一个显示 "Hello, World!" 的应用程序。这个应用程序需要一个标签来展示问候语,以及一个按钮来触发显示。以下是这个小程序的代码:
import tkinter as tk
app = tk.Tk()
app.title("GUI App")
app.geometry("320x240")
label = tk.Label(
app,
font=("Helvetica", 16, "bold"),
)
label.pack()
def callback(text):
def closure():
label.config(text=text)
return closure
button = tk.Button(
app,
text="Greet",
command=callback("Hello, World!"),
)
button.pack()
app.mainloop()
这段代码创建了一个简单的 Tkinter 应用,包含一个窗口,窗口里有一个标签和一个按钮。点击 "Greet" 按钮后,标签会展示 "Hello, World!" 的信息。
callback()
函数会返回一个闭包对象,这个对象可以用来设置按钮的命令参数。这个参数需要一个不接受任何参数的可调用对象。如果你需要像示例那样传递参数,那么可以使用闭包。
Source: https://realpython.com/python-closure/
本文由 mdnice 多平台发布
相关文章:

Python闭包|你应该知道的常见用例(上)
引言 在 Python 编程语言中,闭包通常指的是一个嵌套函数,即在一个函数内部定义的另一个函数。这个嵌套的函数能够访问并保留其外部函数作用域中的变量。这种结构就构成了一个闭包。 闭包在函数式编程语言中非常普遍。在 Python 中,闭包特别有…...

printf影响单片机中断速度
printf是我们常用的调试程序的手段,在第一版程序中,经常会使用printf来验证程序是否工作正确。这样的调试手段应该在正式版的程序发布前注释掉或者删除。而且不当地使用printf也会带来某些功能性问题,例如,在某项目中,…...

JavaScript 23种经典设计模式简介
23种JavaScript经典设计模式 JavaScript经典设计模式 通过之前的学习,我们知道设计模式是一种解决代码组织、代码复用和代码可维护性等问题的技术方法。它通过将代码以特定的方式组织起来,使代码结构更加清晰、可读性更高、易于维护和扩展。为了在开发…...

位运算相关算法
一、异或运算介绍 1、性质介绍 异或运算(XOR,Exclusive OR)是一种位运算符。对于两个位进行异或操作,当且仅当这两个位不同时,结果为 1;如果相同,则结果为 0。 A B A^B00001 1 101110 任何数…...

解决:无法在此设备上激活Windows因为无法连接到你的组织的激活服务器
问题: 桌面右下角会出现这个东西👇 在设置里查看激活状态就会看到👇 解决方法 : 1.打开CMD 搜索CMD,然后以管理员身份运行 2.设置 KMS服务器 1)命令行输入: slmgr /skms kms.03k.org 然后…...

【Spring】——SpringBoot项目创建
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 引入 一:介绍 二:Spring Boot项目创建 0:项目目录 1:…...

聊一聊:ChatGPT搜索引擎会取代谷歌和百度吗?
当地时间 10 月 31 日,OpenAI 正式推出了 ChatGPT 搜索功能,能实时、快速获取附带相关网页来源链接的答案。这一重大升级标志着其正式向谷歌的搜索引擎霸主地位发起挑战。 本周五我们聊一聊: 欢迎在评论区畅所欲言,分享你的观点~ …...

分布式中常见的问题及其解决办法
分布式中常见的问题及其解决办法 一、多个微服务要操作同一个存储在redis中的变量,如何确保这个变量的正确性 答: 在多个微服务操作同一个存储在Redis中的变量时,可以采取以下措施来确保变量的正确性: 1、使用Redis的事务&…...

HTML 基础标签——多媒体标签<img>、<object> 与 <embed>
文章目录 1. `<img>` 标签主要属性示例注意事项2. `<object>` 标签概述主要属性示例注意事项3. `<embed>` 标签概述主要属性示例注意事项小结在现代网页设计中,多媒体内容的使用变得越来越重要,因为它能够有效增强用户体验、吸引注意力并传达信息。HTML 提…...

word mathml 创建粗体字母快捷键
在 mathml 中达到latex中 \mathbf{A} 的效果 由于word本身不支持这个命令,所以打算用快捷键实现 快捷键的功能是加粗光标前一个字目 1. Alt F8 打开宏,如果打不开可以尝试 Alt Fn F8 2. 输入 BoldPreviousCharacter 新建宏: Sub Bold…...

ROOT添加用户提示权限不够
有个系统已经上线过了,之后测评整改要求不能用root启动中间件,我就想着添加一个普通用户,赋予指定目录权限,然后启动应用就行了 。 结果用root用户创建账号提示权限不够,确认了几遍,是root 命令前面加sud…...

关于使用svgIcon 菜单折叠 显示文字情况
使用的工具:vue2,ant design vue 问题: **解决:在<svg-icon> 外面包一层 <a-icon> ** 使用: 在 main.js 中:...

Python使用PDF相关组件案例详解
主要对pdfminer.six、pdfplumber、PyMuPDF、PyPDF2、PyPDF4、pdf2image、camelot-py七个PDF相关组件分别详解,具体使用案例演示 1. pdfminer.six pdfminer.six 是一个专门用来从 PDF 中提取文本的库,能够处理复杂的文本布局,适合用于文本解析…...

day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
并查集理论基础 基础内容 并查集常用来解决连通性问题,主要有两个功能: 将两个元素添加到一个集合中判断两个元素在不在同一个集合 将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将…...

鸿蒙next选择 Flutter 开发跨平台应用的原因
在移动操作系统的竞争中,鸿蒙(HarmonyOS)自从发布以来便吸引了广泛的关注。作为华为主导的操作系统,鸿蒙的设计初衷是打破平台壁垒,实现设备间的无缝连接与应用共享。然而,要实现这一目标,仅仅依…...

shodan6-7---清风
shodan6-7 1.shodan网页版 以cve-2019-0708漏洞指纹特征为例 "\x03\x00\x00\x0b\x06\xd0\x00\x00\x124\x00"在这里插入图片描述 搜索命令参考 https://www.shodan.io/search/filters这个网页中有搜索关键词 对指定网址进行监控,这里可以对ip进行扫描&…...

FTP、ISCSI、CHRONY、DNS、NFS、DOCKER、MARIADB、NGINX、PHP、CA各服务开启方法
2.1 FTP 服务 (vsftpd) 安装 vsftpd: sudo yum install vsftpd -y 启动并设置开机自启: sudo systemctl start vsftpdsudo systemctl enable vsftpd 配置文件位于 /etc/vsftpd/vsftpd.conf,可根据需要修改配置。 2.2 SCSI 服务 SCSI 配…...

抢先体验AI领域的新宠儿:Llama3.1,部署实战探索!
本文简介 就在今天,Meta 发布了 Llama 3.1,这次带来的中杯、大杯和超大杯3个版本。 从纸面数据来看,Llama 3.1 超大杯已经能跟 GPT-4 Omni、Claude 3.5 Sonnet 分庭抗礼了。 而中杯和大杯更是将同量级的对手摁在地上摩擦。 要知道ÿ…...

HarmonyOS基础:鸿蒙系统组件导航Navigation
大家好!我是黑臂麒麟(起名原因:一个出生全右臂自带纹身的高质量程序员😏),也是一位6(约2个半坤年)的前端; 学习如像练武功一样,理论和实践要相结合࿰…...

【K8S问题系列】Kubernetes 中 Pod 无法通过 Service 名称访问服务的 DNS 解析失败【已解决】
在 Kubernetes 中,Service 提供了一种稳定的方式,通过名称访问一组 Pod。当其他 Pod 无法通过 Service 名称访问服务,并且出现 DNS 解析失败时,通常会导致应用无法正常工作。本文将详细分析此问题的常见原因及其解决方案。 一、问…...

【下载工具】Internet Download Manager下载器介绍
Internet Download Manager(简称IDM)作为一款功能强大的下载管理软件,以其高效、稳定的特点受到了广大用户的青睐。本文将为您详细介绍IDM的功能特性以及具体的使用方法。 功能特性 加速下载:IDM通过多线程下载技术,…...

如何打开/关闭 GitLab 的版本检查功能?
本文分享如何打开/关闭 GitLab 的版本检查功能。 极狐GitLab 是 GitLab 的中国发行版【https://dl.gitlab.cn/ncecn6kb】,中文版本对中国用户更友好,文章以私有化部署的极狐GitLab 实例来演示版本检查功能的开启和关闭。强烈不建议关闭该功能࿰…...

java-web-day13-事务管理+spring aop
事务管理: 事务回滚 默认情况下,只有出现runtimeException(运行时异常)才回滚, 而如果出现其他异常,例如受检异常, 就不会回滚事务, 不过可以加上rollbackfor属性用于控制出现何种异常类型, 回滚事务 事务传播: 当一个事务方法被另一个事务方法调用时, 这个事务方法应该如何进行…...

MySQL详细安装教程
一、从MySQL官网安装 可以翻译成中文看起来就舒服多了 下载并打开安装包,能看到版本是8.0.36,双击运行或者右键选择打开,打开后是一个安装向导,这个安装向导会先帮我们安装一个 mysql-installer 的程序,再通过该程序安…...

文件系统和日志管理
一、文件系统 1.概述 文件系统:文件系统提供了一个接口,用户用来访问硬件设备(硬盘)。硬件设备上对文件的管理。文件存储在硬盘上,硬盘最小的存储单位是512字节(扇区)。文件在硬盘上的最小存储…...

【LeetCode】【算法】208. 实现 Trie (前缀树)
LeetCode 208. 实现 Trie (前缀树) 题目描述 Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。 请你实现 Trie 类&…...

libaom 源码分析:帧间运动矢量预测
AV1 帧间运动矢量预测原理 运动矢量可以被相邻块预测,这些相邻块可以是空域相邻块,或位于参考帧中的时域相邻块;通过检查所有这些块,将确定一组运动矢量预测器,并用于编码运动矢量信息。空域运动矢量预测 两组空域相邻块可以被利用寻找空域 MV 预测器,第一组包括当前块的…...

Android TextView自动换行文本显示不全解决
某些情况下,TextView自动换行后,会出现每行结尾处显示不全的问题, 如图: 常见解决方案: 设置TextView的“ellipsize”属性为“end” 实测无效!将TextView外部的Layout改为RelativeLayout 实测无效&…...

【LeetCode】【算法】394. 字符串解码
LeetCode 394. 字符串解码 题目描述 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的;输入字…...

最新整理:Selenium自动化测试面试题
1.selenium中如何判断元素是否存在? find_elements查找到的元素个数为0,find_element报错意味着元素不存在 2.如何判断元素是否出现? 判断元素是否出现,存在两种情况,一种是该元素压根就没有,自然不会出现;另外一种是有这样的…...