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 个内层)
i
是 outer()
的局部变量每轮循环
inner
都捕获 当前值(Python 通过 cell 机制),所以 3 个闭包各持不同 cell3、错误拆解(多层 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 |
无推导式 | 完全用 def 、for 、append 代替 |
3、总结
外层仅有一个作用域,
for
循环每次新建 def
默认参数
_i=i
把当前循环值 冻结进闭包最终得到 3 个独立闭包,行为与原式完全一致