21. 符号表
21.符号表
解析生成 AST 后,CPython 执行范围分析。
此阶段构建符号表。
符号表记录名称在每个范围内的行为方式。它确定名称是本地名称、全局名称、自由名称、单元名称、参数名称、导入名称、注释名称还是从嵌套范围引用的名称。
对于这个来源:```python x = 10
def outer(): y = 20
def inner():
return x + y
return inner
符号表阶段决定:```text
x global from inner
y free variable in inner
y cell variable in outer
inner local variable in outer
outer global variable in module
```这种分析至关重要,因为字节码的生成依赖于它。加载局部变量使用与加载全局变量或闭包变量不同的字节码。
## 21.1 在编译管道中的位置
符号表阶段位于 AST 构建和字节码生成之间。```text
source
↓
tokenization
↓
parsing
↓
AST
↓
symbol table analysis
↓
compiler
↓
code object
↓
bytecode execution
```解析器构建语法结构。
符号表阶段构建范围结构。
然后编译器使用两者。
## 21.2 符号表包含什么
每个作用域都有一个符号表。
符号表存储以下信息:```text
which names exist
where names are assigned
where names are read
whether names are parameters
whether names are imported
whether names are global
whether names are nonlocal
whether names escape into nested scopes
whether names require closure cells
```从概念上讲:```text
Scope: function outer
Symbols:
y local + cell
inner local
Scope: function inner
Symbols:
x global
y free
```符号表阶段不执行代码。它执行静态范围分类。
## 21.3 Python 使用词法作用域
Python 使用词法作用域。
嵌套函数可以访问封闭范围内的名称。
例子:```python
def outer():
x = 10
def inner():
return x
return inner
inner可以访问x因为x存在于封闭的词汇环境中。
符号表阶段在运行时执行之前确定这种关系。
如果没有词法作用域分析,CPython 将不知道:```text where to look for a name whether to allocate closure storage whether a name belongs in locals whether a variable escapes into nested functions
Python 有多种作用域类型。
|范围 |示例|
| ------------------- | ----------------- |
|模块范围 |顶级文件 |
|功能范围|`def f():`|
|班级范围|`class C:`|
|领悟范围|`[x for x in xs]`|
|发电机范围 |`(x for x in xs)`|
| Lambda 范围 |`lambda x: x + 1`|
每个范围都有独立的符号分析。
例子:```python
x = 1
def f():
x = 2
return x
```模块和功能各有各的`x`。
符号表将它们分开。
## 21.5 模块范围
顶级代码在模块范围内执行。
例子:```python
x = 1
y = 2
def add():
return x + y
```在模块范围内:```text
x global/module-local
y global/module-local
add global/module-local
```里面`add`:
```text
x global
y global
```在运行时,全局变量存储在模块字典中。
符号表阶段决定了里面的引用`add`应该使用全局查找字节码而不是本地查找字节码。
## 21.6 函数作用域
函数体创建局部作用域。
例子:```python
def f(a, b):
c = a + b
return c
```里面`f`:
```text
a parameter + local
b parameter + local
c local
```参数被视为已初始化的局部变量。
编译器稍后为框架对象内的局部变量分配槽。
符号表阶段决定:```text
how many local variables exist
which names are parameters
which bytecode instructions to emit
```## 21.7 局部变量
除非另有声明,否则在函数中任何位置分配的名称都将成为本地名称。
例子:```python
x = 10
def f():
print(x)
x = 20
```这引发了:```text
UnboundLocalError
```为什么?
因为符号表阶段看到分配给`x`里面`f`:
```python
x = 20
```这意味着:```text
x is local to f
```那么早些时候:```python
print(x)
```尝试阅读本地`x`分配之前。
此行为来自静态范围分析,而不是运行时猜测。
## 21.8 全局声明`global`更改范围分类。
例子:```python
x = 10
def f():
global x
x = 20
```现在符号表记录:```text
x global
```里面`f`。
该赋值更新了模块级变量。
没有`global`,赋值将创建一个局部变量。
符号表阶段必须处理`global`编译名称访问之前的声明。
## 21.9 非局部声明`nonlocal`指封闭函数作用域变量。
例子:```python
def outer():
x = 10
def inner():
nonlocal x
x += 1
inner()
return x
```里面`inner`:
```text
x free variable
```里面`outer`:
```text
x cell variable
```单元变量是由嵌套作用域捕获的局部变量。
没有`nonlocal`, 里面的赋值`inner`将创建一个新的局部变量。`nonlocal`告诉符号表:```text
do not create a local binding
use enclosing function binding
```## 21.10 自由变量
自由变量是在作用域内使用但在封闭函数作用域中定义的名称。
例子:```python
def outer():
x = 10
def inner():
return x
```里面`inner`:
```text
x free variable
x不是本地的inner。
它来自outer。
自由变量需要闭包支持。
21.11 单元格变量
单元格变量是由嵌套作用域引用的局部变量。
例子:```python def outer(): x = 10
def inner():
return x
```里面outer:
x local + cell
```为什么?
因为`x`之后必须生存`outer`返回。
嵌套函数仍然需要访问它。
CPython 将捕获的变量存储在堆分配的闭包单元中,而不是普通的仅堆栈局部变量中。
符号表阶段确定哪些局部变量需要单元存储。
## 21.12 关闭
闭包直接取决于符号表分析。
例子:```python
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
```符号表确定:```text
count in make_counter:
local + cell
count in inc:
free
```The compiler then generates closure machinery.
运行时:```python
c = make_counter()
```返回一个函数,该函数携带对捕获的访问权限`count`细胞。
如果没有符号表分析,闭包构造将是不可能的。
## 21.13 名称解析类别
CPython 将名称大致分为几类。
|类别 |意义|
| ---------------- | -------------------------------- |
|本地|在当前范围内定义 |
|全球显式|声明为`global`|
|全局隐式 |在模块/内置级别解决 |
|免费|来自封闭范围 |
|细胞|嵌套范围捕获的本地 |
此分类控制字节码生成。
字节码类别示例:
|类别 |字节码家族 |
| ----------- | ---------------- |
|本地|`LOAD_FAST`|
|全球|`LOAD_GLOBAL`|
|免费/手机|`LOAD_DEREF`|
|姓名查询 |`LOAD_NAME`|
不同的范围决策会产生不同的运行时行为和性能。
## 21.14 字节码依赖范围分析
示例:```python
def f(a):
return a
```字节码:```text
LOAD_FAST a
RETURN_VALUE
a是本地的。
现在:```python x = 1
def f():
return x
字节码:text
LOAD_GLOBAL x
RETURN_VALUE
现在关闭案例:python
def outer():
x = 1
def inner():
return x
```里面inner:
LOAD_DEREF x
RETURN_VALUE
```仅解析器无法确定这些指令。
符号表阶段提供所需的范围信息。
## 21.15 类范围
类体具有特殊的作用域行为。
例子:```python
x = 1
class C:
y = x
```类体内:```text
y local to class namespace
x global
```类体在它们自己的命名空间字典中执行。
但方法不会自动从词法上捕获类范围变量。
例子:```python
class C:
x = 1
def f(self):
return x
```这个不能访问`C.x`。
名称`x`里面`f`除非另有定义,否则被视为全局。
类作用域与函数作用域在一些重要方面有所不同。
## 21.16 理解范围
现代 Python 推导式创建了自己的作用域。
例子:```python
x = 100
values = [x for x in range(3)]
print(x)
```输出:```text
100
```理解变量不会泄漏到外部作用域中。
内部:```text
comprehension creates nested scope
x inside comprehension is local there
outer x remains unchanged
```早期的 Python 版本的行为有所不同。现代 CPython 使用隔离理解范围。
## 21.17 生成器表达式范围
生成器表达式还创建嵌套范围。
例子:```python
x = 10
g = (x for x in range(3))
print(x)
```外`x`保持不变。
生成器表达式的行为与隐式嵌套函数类似。
符号表阶段为生成器范围创建单独的符号信息。
## 21.18 Lambda 范围
Lambda 表达式创建函数作用域。
例子:```python
x = 10
f = lambda y: x + y
```lambda 内部:```text
y local parameter
x free variable
```Lambda 是匿名函数,但符号表分析将它们与普通嵌套函数类似地对待。
## 21.19 导入和符号表
导入也会影响符号表。
例子:```python
import os
from math import sin
```模块范围符号:```text
os imported local/global
sin imported local/global
```内部功能:```python
def f():
import json
json成为本地f。
从范围分析的角度来看,导入是指派。
21.20 注释
变量注释参与符号处理。
例子:python x: int = 1 符号表记录:text x assigned 函数注释也出现:```python
def f(x: int) -> str:
return str(x)
## 21.21 异常处理程序变量
异常处理程序名称创建本地绑定。
例子:```python
try:
run()
except ValueError as e:
print(e)
```异常块内:```text
e local
```CPython 随后会在处理程序之后清除异常变量,以避免涉及回溯的引用循环。
符号表阶段标识绑定本身。清理行为稍后发生。
## 21.22 模式匹配绑定
模式匹配引入了绑定规则。
例子:```python
match value:
case [x, y]:
print(x, y)
```模式变量成为本地绑定。
符号表阶段必须区分:```text
pattern capture
ordinary expression
```因为模式语法具有不同的语义。
## 21.23 符号分析期间的 AST 遍历
符号表阶段遍历 AST。
简化模型:```text
visit Module
create module scope
visit FunctionDef
register function name
create child scope
visit parameters
visit body
visit Assign
mark targets as assigned
visit expression
visit Name
mark as load/store/delete
visit Global
record explicit global declaration
visit Nonlocal
record nonlocal declaration
```遍历首先收集信息,然后解析范围关系。
## 21.24 两次范围分析的性质
范围分析并不纯粹是局部的。
例子:```python
def f():
print(x)
x = 1
```第一个的意义`x`取决于以后的分配。
因此,CPython 无法在单次从左到右的传递中对名称进行分类。
在最终分类之前,符号表阶段有效地收集整个块的范围信息。
## 21.25 嵌套作用域树
范围形成一棵树。
例子:```python
x = 0
def outer():
y = 1
def inner():
z = 2
```范围树:```text
Module
outer
inner
```每个作用域都存储到子作用域的链接。
自由变量解析通过封闭范围向外走。
## 21.26 内置函数
如果名称既不是本地模块定义的也不是全局模块定义的,则运行时查找可能会回退到内置函数。
例子:```python
print(len([1, 2, 3]))
```模块范围内:```text
print implicit global/builtin
len implicit global/builtin
```符号表阶段不解析实际的内置对象。它对查找样式进行分类。
在运行时,全局查找检查:```text
module globals
then builtins
```## 21.27 的`symtable`模块
Python 通过以下方式公开符号表`symtable`模块。
例子:```python
import symtable
src = """
x = 1
def outer():
y = 2
def inner():
return x + y
"""
table = symtable.symtable(src, "<input>", "exec")
print(table.get_identifiers())
```可以通过编程方式检查嵌套符号表。
该模块可用于:```text
linters
static analyzers
teaching scope behavior
compiler experiments
```## 21.28 CPython 中的符号表数据结构
重要的来源领域包括:```text
Python/symtable.c
Include/internal/
Python/compile.c
```符号表机械存储:```text
scope type
symbol flags
child scopes
free variables
cell variables
parameter information
optimization flags
```符号标志描述了以下行为:```text
assigned
used
parameter
global
nonlocal
free
cell
imported
annotated
```编译器稍后会使用此元数据。
## 21.29 优化和局部变量
范围分析可以实现快速的局部变量访问。
局部变量使用帧内的索引槽。
例子:```python
def f(a, b):
c = a + b
return c
```局部变量可以编译成数组样式的访问:```text
locals[0] -> a
locals[1] -> b
locals[2] -> c
```这比字典查找快得多。
如果没有符号表分析,CPython 就无法预先计算本地布局。
## 21.30 最小心智模型
使用这个模型:```text
The parser builds syntax structure.
The symbol table phase builds scope structure.
Each module, function, class, lambda, and comprehension creates a scope.
Names are classified as local, global, free, or cell variables.
Nested functions create closure relationships.
The compiler uses symbol table information to generate correct bytecode.
Fast locals, globals, and closures all depend on symbol analysis.
```符号表阶段是 CPython 将语法转换为可执行范围语义的阶段。