Python 闭包:来自 JavaScript
抱歉,我需要您提供要翻译的具体文本内容。请粘贴或输入您希望翻译成简体中文的文章正文,我会按照您的要求保留来源链接、格式和技术术语进行翻译。
用书籍与视频学习
“书籍——深入了解某个主题的知识
视频——快速上手使用特定技术”
我仍然会在想学习新东西时拿起一本书。虽然当今的文档、Udemy 课程以及其他视频资源非常宝贵,但文字(无论是电子版还是纸质版)总有一种更持久的感觉。
今年我的目标之一是提升我的 Python 技能,以便更轻松地构建 AI/ML 解决方案。在这段旅程中,一个关键环节是理解 Python 中的闭包。
以下大部分内容摘自 Fluent Python(第2版) 和 Python Cookbook(第3版)——强烈推荐的实用且引人入胜的 Python 指南。
为什么闭包很重要
- 装饰器 – Python 装饰器是基于闭包构建的(稍后会详细说明)。
- 回调 – 像 JavaScript 回调一样,闭包让你能够“记住”状态。
- 工厂函数 – 动态创建专用函数(例如,一个乘法器)。
- 数据封装 – 在不使用类的情况下隐藏内部状态。
如果你曾在 Flask 中写过 @app.route(),那么你已经使用了闭包——即使当时没有意识到。
闭包定义
一个嵌套函数,它能够记住并访问其封闭(外部)函数作用域中的变量,即使外部函数已经执行完毕,也能让内部函数保持状态。
基本示例:乘数
Python 实现 (mult_closure.py)
def make_multiplier_of(n):
"""Outer function that takes a factor `n` and returns a closure."""
def multiplier(x):
"""Inner function (closure) that uses the remembered factor `n`."""
return x * n
return multiplier
# Create two closures, each remembering a different `n` value
doubler = make_multiplier_of(2) # remembers n = 2
tripler = make_multiplier_of(3) # remembers n = 3
# Results – the scalar is remembered, so we only pass the value to be multiplied
print(f"8 times 2 is {doubler(8)}")
print(f"4 times 3 is {tripler(4)}")
输出
8 times 2 is 16
4 times 3 is 12
解释: 外部函数返回嵌套的 multiplier 函数,该函数保留 n 的值。
可视化调用方式
# Saved to a variable
doubler = make_multiplier_of(2)
doubler(4)
# Executed in‑place
make_multiplier_of(2)(4)
- 保存到变量
- 就地执行
JavaScript 中的相同思路
JavaScript 实现 (mult_closure.js)
function makeMultiplierOf(n) {
// Outer function that takes a factor `n` and returns a closure
function multiplier(x) {
// Inner function (closure) that uses the remembered factor `n`
return x * n;
}
return multiplier;
}
// Instances of our new closure
const doubler = makeMultiplierOf(2);
const tripler = makeMultiplierOf(3);
// Results – the closure remembers the initial value it was passed
console.log(`8 times 2 is ${doubler(8)}`);
console.log(`4 times 3 is ${tripler(4)}`);
在调用之间保持状态
闭包可以保存可变数据,使其在多次调用之间持久化。下面是一个日志记录器,它将消息存储在外部函数作用域内的列表中。
Python logger closure (logger_closure.py)
def create_logger(source):
"""Outer function that holds a list of logs for a given `source`."""
logs = []
def log_message(message=None):
"""
Inner function that appends a message (if provided) and always returns
the current list of logs.
"""
if message:
logs.append({"source": source, "message": message})
return logs
return log_message
# Two independent loggers
error_log = create_logger("error")
info_log = create_logger("info")
# Log some messages
info_log("Hello world")
error_log("File Not Found")
# Retrieve logs
print(error_log("Zero Division"))
print(info_log())
输出
[{'source': 'error', 'message': 'File Not Found'},
{'source': 'error', 'message': 'Zero Division'}]
[{'source': 'info', 'message': 'Hello world'}]
关键点:每次调用 create_logger 都会创建自己的 logs 列表。虽然 error_log 和 info_log 共享相同的闭包结构,但它们维护的是独立的状态。
Python的变量作用域 vs. JavaScript
- JavaScript (pre‑ES6) 依赖
var,需要仔细处理函数作用域。现代 JS 使用let和const来创建块级作用域变量。 - Python 没有直接对应
let/const的概念。相反,它遵循 LEGB 规则(Local → Enclosing → Global → Built‑in)进行名称解析。理解该规则在使用闭包时至关重要。
TL;DR
- 闭包让嵌套函数记住其外层作用域的值。
- 它们是装饰器、回调、工厂函数以及简单有状态对象的基础。
- Python 的词法作用域(LEGB)在你掌握该规则后,使闭包变得直观。
祝编码愉快! 🚀
Example 4 – legb_overview.py: LEGB 规则的可视化示例
"""Global scope"""
x = "global"
def outer():
"""Enclosing scope"""
y = "enclosing"
def inner():
"""Local scope"""
z = "local" # Local scope
# Python searches: Local → Enclosing → Global → Built‑in
print(z) # Found in Local
print(y) # Not in Local, found in Enclosing
print(x) # Not in Local/Enclosing, found in Global
print(len) # Not in Local/Enclosing/Global, found in Built‑in
inner()
outer()
Output
local
enclosing
global
LEGB 搜索工作原理
| 作用域 | 描述 |
|---|---|
| Local(局部) | 解释器首先检查当前函数的局部命名空间。 |
| Enclosing(闭包) | 如果函数是嵌套的,Python 会向上一级查找封闭函数的命名空间(在闭包中使用频繁)。 |
| Global(全局) | 接着检查模块级命名空间(文件顶部定义的变量或导入的变量)。 |
| Built‑in(内建) | 最后检查 Python 的内建命名空间(例如 len、print)。 |
重要提示: 一旦找到匹配的名称,Python 会立即停止搜索。找到后 不会 继续向外层作用域查找。
nonlocal 关键字
示例 5 – make_avg_err.py: 不使用 nonlocal 的闭包
"""Before"""
def make_avg():
count = 0
total = 0
def inner(new_val):
count += 1 # `nonlocal` is required when you need to **rebind** a variable from an enclosing (but not global) scope. It works for immutable objects like `int`, `float`, `str`, etc.
对于可变对象(例如列表),可以在不使用
nonlocal的情况下修改对象的内容,因为你并没有重新绑定名称本身(my_list.append(item))。
给 JavaScript 开发者的简要提示
从 JavaScript 转到 Python 时,往往会直接套用相同的模式。然而,Python 的作用域规则——尤其是闭包和 nonlocal 关键字的行为——有所不同。理解这些细微差别可以避免潜在的 bug,并让你的代码更“Pythonic”。
接下来是什么?
我目前正在构建一个 AST File Parser,以探索许多 Python 中提升效率的“细小之处”。我的目标是在本月末完成该项目,之后我将深入研究其他 Python 主题,例如:
- Generators
- Decorators
- Special (magic) methods
如果你想讨论这些概念,欢迎随时联系!