Python函数-循环变量闭包陷阱详解
2025-8-31
| 2025-8-31
Words 1217Read Time 4 min
type
status
date
slug
summary
tags
category
icon
password

📝 列表推导式的作用域

1、静态外层作用域(Lexical Enclosing Scope)

💡
列表推导式在语法上位于某个“外层作用域”内部,这个外层作用域可能是:
  • 模块级别(global 作用域)
  • 函数内部(local 作用域)
  • 类的命名空间(class body)
  • 另一个推导式或生成器表达式内部(嵌套推导式)
“外层作用域”就是静态词法作用域,即包含该推导式的最小可执行代码块的作用域。

2、列表推导式自己的局部作用域

在 Python 3 中,列表推导式相当于隐式地生成了一个嵌套函数,其伪代码结构大致如下:
这个 _listcomp 就是推导式自己的局部作用域。
循环变量 x、列表名 _result 等都只存在于该局部作用域中,推导式执行完后就被回收。

3、变量查找顺序(LEGB 规则)

💡
列表推导式在执行时,如果内部引用了某个未在推导式内部绑定的变量名,Python 会按照 LEGB 规则依次查找:
  • Local:推导式内部(循环变量、推导式内部临时变量)
  • Enclosing:包含该推导式的函数作用域(如果有)
  • Global:模块级别
  • Built-in:内建命名空间

4、常见疑问与陷阱

4.1、循环变量不会泄漏

4.2、类作用域的特例

在类体中定义的推导式无法直接访问类作用域中的变量,因为类体本身并不创建闭包作用域:

5、总结

在 Python 3 中,列表推导式“所在的外层作用域”就是静态语法上包含它的那个作用域(函数、模块等);而推导式本身会额外创建一个局部作用域,循环变量不会泄漏,但仍能读取外层变量。

📝 循环变量闭包拆解

1、原始匿名写法

列表推导式所在的外层作用域就是当前模块(或函数)
因此只有一个变量 i 被所有 lambda 共享,形成 1 个 cell
列表推导式本身不是函数,lambda 的闭包引用的是 推导式外部的 i,而不是推导式内部每次循环的局部 i

2、正确拆解(1 个外层、3 个内层)

iouter() 的局部变量
每轮循环 inner 都捕获 当前值(Python 通过 cell 机制),所以 3 个闭包各持不同 cell

3、错误拆解(多层 outer)

这里把 for 写在 outer 内部,但 每次循环都 return,导致只生成 1 个闭包就退出,语义完全不符。

4、总结

原式中的 lambda 闭包引用的是列表推导式外部作用域的 i
正确拆解应保持 1 个外层函数 + 循环内新建 3 个闭包
多层 outer 会导致提前返回,破坏了“3 个闭包”的数量和共享作用域。

📝 “语法级”修复

1、原始匿名写法

外层作用域:当前模块(或当前函数)。
列表推导式遍历range(3) → i 取值 0,1,2
循环变量i 只有一个实例,但 每次 lambda 通过默认参数 i=i 把当前值冻结下来,于是得到 3 个 独立闭包,分别捕获 0,1,2

2、等价拆解:无匿名、无推导式

步骤
说明
外层
只有一个作用域(outer 的局部变量 i
默认参数
每轮循环把当前 i 值写入 inner 的默认参数,生成 3 个独立 cell
无推导式
完全用 defforappend 代替

3、总结

外层仅有一个作用域,for 循环每次新建 def
默认参数 _i=i 把当前循环值 冻结进闭包
最终得到 3 个独立闭包,行为与原式完全一致
 
  • 开发
  • Python
  • Python函数-生成器拓展Python函数-模块
    Loading...