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 解析失败时,通常会导致应用无法正常工作。本文将详细分析此问题的常见原因及其解决方案。 一、问…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...
