当前位置: 首页 > 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;通常会导致应用无法正常工作。本文将详细分析此问题的常见原因及其解决方案。 一、问…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...