Python 闭包:来自 JavaScript

发布: (2026年1月20日 GMT+8 21:00)
8 min read
原文: Dev.to

抱歉,我需要您提供要翻译的具体文本内容。请粘贴或输入您希望翻译成简体中文的文章正文,我会按照您的要求保留来源链接、格式和技术术语进行翻译。

用书籍与视频学习

“书籍——深入了解某个主题的知识
视频——快速上手使用特定技术”

我仍然会在想学习新东西时拿起一本书。虽然当今的文档、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_loginfo_log 共享相同的闭包结构,但它们维护的是独立的状态。

Python的变量作用域 vs. JavaScript

  • JavaScript (pre‑ES6) 依赖 var,需要仔细处理函数作用域。现代 JS 使用 letconst 来创建块级作用域变量。
  • 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 的内建命名空间(例如 lenprint)。

重要提示: 一旦找到匹配的名称,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

如果你想讨论这些概念,欢迎随时联系!

Back to Blog

相关文章

阅读更多 »