Python 名称绑定:循环、闭包与默认值
用完整历史案例解释名称读取时机,并限定默认值捕获与对象共享的作用。
假定读者能够读函数、循环和列表,需要解释下面三个函数为何返回相同值。lambda: i 创建一个没有参数、调用时返回 i 的函数;append 将这个函数加入列表。
def make_readers():
readers = []
for i in range(3):
readers.append(lambda: i)
return readers
print([reader() for reader in make_readers()])
# 既有案例在 Python 3.14.6 的记录输出:[2, 2, 2]这个模型里有三个对象或关系需要先认识:make_readers 的一次调用建立外层环境,环境里有局部名称 i,列表里保存三个随后可以调用的函数。
循环依次让同一个局部名称 i 对应整数 0、1、2。这个 for 循环没有给每轮创建独立作用域。创建函数时,函数体里的 i 还没有求值;三个函数都可以从这次调用的外层环境取得该名称。循环结束后才执行列表中的函数,此时 i 对应 2,所以得到三个 2。Python 官方 FAQ 明确区分定义与调用的时机,并说明普通 def 函数也具有相同行为。Python 名称与解析
函数返回后,这些函数仍可以访问它们所需的外层环境。这里把函数及其可继续访问的相关外层环境称为闭包。先有“哪个名称、哪个环境、何时读取”的具体关系,再使用闭包和延迟绑定等名称,术语才有可识别的对象。
如果目标变为让各函数在无参数调用时使用创建当轮的整数,可以把创建表达式改为:
readers.append(lambda value=i: value)每次定义这个函数时,默认值表达式 i 会求值,三个函数分别得到整数默认值 0、1、2。调用时若没有传入 value,函数体使用自己的参数默认值。原案例在相同环境中记录的结果为 [0, 1, 2]。这个解释需要同时说明“定义时求默认值”和“调用时读参数”,才能看出改动改变了哪个联系。默认值的求值时机由 Python 官方教程说明。默认参数
同一规则还能说明函数形式、调用时机与对象性质的变化。下列为 Python 3.14.6 的历史结果:
| 改变的条件 | 2026-10-04 记录结果 | 对解释的检验 |
|---|---|---|
每轮用普通 def 定义读取 i 的函数,循环结束后调用 | [2, 2, 2] | 名称读取规则没有仅限于 lambda |
| 当轮立即调用新函数,循环结束再调用全部 | 当时 [0, 1, 2],随后 [2, 2, 2] | 同一名称在不同调用时机可能对应不同值 |
默认值取列表 [0],之后把该对象元素改为 9,再读取该元素 | 返回 9 | 保存的是对象;默认参数没有承担深复制或冻结对象的作用 |
这些变化补足了待审句“改用默认参数做 early binding”的边界,防止把当前改法推广成无条件口诀。它们支持样例行为,没有观察目标读者的理解,也没有评价 CPython 内部表示。
讲到这里,名称、环境、读取时机和默认值已足以解释目标行为。读者若要分析 CPython 的内部表示,才需要继续展开内部结构。当前判断不依赖这些细节,主路径可以在这里停下。
复查材料与历史边界
案例检查脚本保留函数、变例与断言。脚本另包含 SQLite 查询案例,语言规则和 SQL 规则分别判断。下载后运行 python3 case-checks.py,先读取输出中的实际 Python 和 SQLite 版本。
2026-10-04 原记录的 Python 3.14.6、SQLite 3.53.4 下,脚本断言全部通过。2026-10-05 重组读取旧正文与脚本,未重跑这组历史环境;旧观察支持指定样例,未覆盖所有 Python 实现、内部表示或真人理解。官方版本族文档可能随补丁更新,案例版本仍以实际记录为准。
最后更新于