当前位置: 首页 > news >正文

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

alt

引言

在 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 闭包需要以下元素:
  1. 一个外部或封闭函数:这个函数包含了另一个函数,通常称为内部函数。外部函数可以接收参数,并定义一些内部函数可以访问和修改的变量。
  2. 仅在外部函数中有效的局部变量:这些变量定义在外部函数的作用域内。Python 会保留这些变量,使得即使在外部函数执行完毕后,你仍然可以在闭包中使用它们。
  3. 一个内部或嵌套函数:这个函数定义在外部函数内部,它能够访问并修改外部函数中的变量,哪怕外部函数已经执行完毕。

在这个部分的例子中,你有一个外部函数、一个局部变量(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_arglocal_varanother_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(24)
>>> 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() 函数会返回一个闭包对象,这个对象可以用来设置按钮的命令参数。这个参数需要一个不接受任何参数的可调用对象。如果你需要像示例那样传递参数,那么可以使用闭包。

Reference
[1]

Source: https://realpython.com/python-closure/

本文由 mdnice 多平台发布

相关文章:

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

引言 在 Python 编程语言中&#xff0c;闭包通常指的是一个嵌套函数&#xff0c;即在一个函数内部定义的另一个函数。这个嵌套的函数能够访问并保留其外部函数作用域中的变量。这种结构就构成了一个闭包。 闭包在函数式编程语言中非常普遍。在 Python 中&#xff0c;闭包特别有…...

printf影响单片机中断速度

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

JavaScript 23种经典设计模式简介

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

位运算相关算法

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

解决:无法在此设备上激活Windows因为无法连接到你的组织的激活服务器

问题&#xff1a; 桌面右下角会出现这个东西&#x1f447; 在设置里查看激活状态就会看到&#x1f447; 解决方法 &#xff1a; 1.打开CMD 搜索CMD&#xff0c;然后以管理员身份运行 2.设置 KMS服务器 1&#xff09;命令行输入&#xff1a; slmgr /skms kms.03k.org 然后…...

【Spring】——SpringBoot项目创建

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

聊一聊:ChatGPT搜索引擎会取代谷歌和百度吗?

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

分布式中常见的问题及其解决办法

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

HTML 基础标签——多媒体标签<img>、<object> 与 <embed>

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

word mathml 创建粗体字母快捷键

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

ROOT添加用户提示权限不够

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

关于使用svgIcon 菜单折叠 显示文字情况

使用的工具&#xff1a;vue2&#xff0c;ant design vue 问题&#xff1a; **解决&#xff1a;在<svg-icon> 外面包一层 <a-icon> ** 使用: 在 main.js 中&#xff1a;...

Python使用PDF相关组件案例详解

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

day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)

并查集理论基础 基础内容 并查集常用来解决连通性问题&#xff0c;主要有两个功能&#xff1a; 将两个元素添加到一个集合中判断两个元素在不在同一个集合 将三个元素A&#xff0c;B&#xff0c;C &#xff08;分别是数字&#xff09;放在同一个集合&#xff0c;其实就是将…...

鸿蒙next选择 Flutter 开发跨平台应用的原因

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

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这个网页中有搜索关键词 对指定网址进行监控&#xff0c;这里可以对ip进行扫描&…...

FTP、ISCSI、CHRONY、DNS、NFS、DOCKER、MARIADB、NGINX、PHP、CA各服务开启方法

2.1 FTP 服务 (vsftpd) 安装 vsftpd&#xff1a; sudo yum install vsftpd -y 启动并设置开机自启&#xff1a; sudo systemctl start vsftpdsudo systemctl enable vsftpd 配置文件位于 /etc/vsftpd/vsftpd.conf&#xff0c;可根据需要修改配置。 2.2 SCSI 服务 SCSI 配…...

抢先体验AI领域的新宠儿:Llama3.1,部署实战探索!

本文简介 就在今天&#xff0c;Meta 发布了 Llama 3.1&#xff0c;这次带来的中杯、大杯和超大杯3个版本。 从纸面数据来看&#xff0c;Llama 3.1 超大杯已经能跟 GPT-4 Omni、Claude 3.5 Sonnet 分庭抗礼了。 而中杯和大杯更是将同量级的对手摁在地上摩擦。 要知道&#xff…...

HarmonyOS基础:鸿蒙系统组件导航Navigation

大家好&#xff01;我是黑臂麒麟&#xff08;起名原因&#xff1a;一个出生全右臂自带纹身的高质量程序员&#x1f60f;&#xff09;&#xff0c;也是一位6&#xff08;约2个半坤年&#xff09;的前端&#xff1b; 学习如像练武功一样&#xff0c;理论和实践要相结合&#xff0…...

【K8S问题系列】Kubernetes 中 Pod 无法通过 Service 名称访问服务的 DNS 解析失败【已解决】

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

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...