20. AST
#20. AST
抽象语法树,通常称为AST,是Python源代码解析后的结构化表示。
标记器生成扁平的标记流。解析器识别语法。 AST 将结果记录为语句和表达式的树。
对于这个来源:python x = 1 + 2 AST 的形状如下:```text
Module
Assign
targets:
Name id="x", ctx=Store
value:
BinOp
left: Constant value=1
op: Add
right: Constant value=2
## 20.1 在编译管道中的位置
AST 位于解析和编译器分析之间。```text
source bytes
↓
tokenization
↓
parsing
↓
AST
↓
AST validation
↓
symbol table
↓
compiler
↓
code object
↓
bytecode execution
```解析器构建 AST。后续阶段会消耗它。
AST 回答以下问题:```text
What statements are in this module?
What expression is assigned to this name?
What is the body of this function?
What names are loaded, stored, or deleted?
What is the test expression of this if statement?
What is the element expression of this comprehension?
What source position produced this node?
```它不回答以下问题:```text
Is this name local or global?
What bytecode should be emitted?
What stack size is needed?
What constants should be placed in co_consts?
What exception table entries are needed?
```这些属于后面的编译器阶段。
## 20.2 具体语法树与抽象语法树
解析器可以生成具体语法树或抽象语法树。
具体的语法树保留了语法细节。它密切代表了来源。
抽象语法树保留语义结构。它删除了后续编译器阶段不需要的细节。
例如:```python
x = (((1)))
```具体语法包含三对括号。
AST 可以简单地表示为:```text
Assign
target: Name "x"
value: Constant 1
```括号会影响解析,但它们本身不会创建运行时行为。
同样地:```python
x = 1 + 2
```AST 保留添加,因为它会影响运行时行为。```text
BinOp
left: Constant 1
op: Add
right: Constant 2
```因此,AST 在实际编译器意义上是抽象的。它保留操作,而不是所有拼写。
## 20.3 Python 级 AST 访问
Python 通过以下方式公开 AST 功能`ast`模块。
例子:```python
import ast
tree = ast.parse("x = 1 + 2\n")
print(ast.dump(tree, indent=4))
```典型输出:```text
Module(
body=[
Assign(
targets=[
Name(id='x', ctx=Store())],
value=BinOp(
left=Constant(value=1),
op=Add(),
right=Constant(value=2)))],
type_ignores=[])
```这与 CPython 内部使用的通用结构相同,公开为 Python 对象。
这`ast`模块可用于:```text
static analysis
code generation
linting
refactoring
macro-like source transforms
security review tools
documentation tools
teaching compiler structure
```但公共 AST API 仍然是一个抽象。 CPython 的内部 C 表示和公共 Python AST 类是相关的,但它们不是相同的物理对象。
## 20.4 模块节点
普通 Python 文件的根目录是`Module`。
例子:```python
x = 1
print(x)
```AST 形状:```text
Module
body:
Assign
Expr
type_ignores:
[]
```这`body`是一个有序的语句列表。
在模块级别,编译器发出从上到下执行语句的字节码。
根据解析模式有不同的根节点类型:
|解析模式 |根节点|示例|
| ------------------ | -------------- | -------------------- |
|`exec` | `Module` | `x = 1` |
| `eval` | `Expression` | `1 + 2` |
| `single` | `Interactive`| REPL 输入 |
|功能类型模式|`FunctionType`|遗留类型评论 |
例子:```python
import ast
print(type(ast.parse("x = 1", mode="exec")).__name__)
print(type(ast.parse("1 + 2", mode="eval")).__name__)
print(type(ast.parse("print(1)", mode="single")).__name__)
```语法启动模式改变根 AST 形状。
## 20.5 语句节点
陈述代表行动。
常见的语句节点包括:
|节点|源码示例|
| ------------------ | ------------------------ |
|`Assign` | `x = 1` |
| `AnnAssign` | `x: int = 1` |
| `AugAssign` | `x += 1` |
| `Return` | `return x` |
| `If` | `if x: ...` |
| `For` | `for x in xs: ...` |
| `While` | `while x: ...` |
| `FunctionDef` | `def f(): ...` |
| `AsyncFunctionDef` | `async def f(): ...` |
| `ClassDef` | `class C: ...` |
| `Try` | `try: ... except: ...` |
| `With` | `with open(p) as f: ...` |
| `Import` | `import os` |
| `ImportFrom` | `from os import path` |
| `Expr` | `f()`作为声明 |
|`Pass` | `pass` |
| `Break` | `break` |
| `Continue` | `continue` |
| `Raise` | `raise ValueError()` |
| `Assert` | `assert x` |
| `Delete` | `del x` |
| `Global` | `global x` |
| `Nonlocal` | `nonlocal x`|
语句节点出现在语法需要语句的位置。语句节点通常位于`body`, `orelse`, `finalbody`,或类似的列表。
例子:```python
if x:
a = 1
else:
a = 2
```AST 形状:```text
If
test: Name "x", Load
body:
Assign a = 1
orelse:
Assign a = 2
```这`else`块表示为`orelse`场地。
## 20.6 表达式节点
表达式产生值。
常见的表达节点包括:
|节点|源码示例|
| -------------- | ------------------ |
|`Constant` | `1`, `"x"`, `None` |
| `Name` | `x` |
| `BinOp` | `a + b` |
| `UnaryOp` | `-x` |
| `BoolOp` | `a and b` |
| `Compare` | `a < b` |
| `Call` | `f(x)` |
| `Attribute` | `obj.name` |
| `Subscript` | `xs[i]` |
| `List` | `[1, 2]` |
| `Tuple` | `(1, 2)` |
| `Dict` | `{"a": 1}` |
| `Set` | `{1, 2}` |
| `ListComp` | `[x for x in xs]` |
| `GeneratorExp` | `(x for x in xs)` |
| `Lambda` | `lambda x: x + 1` |
| `IfExp` | `a if cond else b` |
| `Yield` | `yield x` |
| `Await` | `await task` |
| `NamedExpr` | `(x := f())`|
例子:```python
result = obj.method(x, y=2)
```AST 形状:```text
Assign
target:
Name "result", Store
value:
Call
func:
Attribute
value: Name "obj", Load
attr: "method"
ctx: Load
args:
Name "x", Load
keywords:
keyword arg="y", value=Constant 2
```AST 记录调用目标、位置参数、关键字参数和属性访问。
## 20.7 表达式上下文
一些表达式节点携带上下文。
最重要的上下文是:
|背景 |意义|
| -------- | -------------------------- |
|`Load`|读取值 |
|`Store`|分配给目标 |
|`Del`|删除绑定或目标 |
例子:```python
x = x + 1
```AST 形状:```text
Assign
target:
Name "x", Store
value:
BinOp
left: Name "x", Load
op: Add
right: Constant 1
```一样的写法,`x`,出现两次,含义不同。
带属性的示例:```python
obj.value = obj.value + 1
```AST 形状:```text
Assign
target:
Attribute obj.value, Store
value:
BinOp
left: Attribute obj.value, Load
op: Add
right: Constant 1
```上下文对于代码生成至关重要。加载名称、存储名称和删除名称使用不同的字节码指令。
## 20.8 常数
现代 Python AST 使用`Constant`对于许多字面值。
示例:```python
1
3.14
"hello"
b"bytes"
None
True
False
...
```每个都可以显示为:```text
Constant(value=...)
```容器文字是单独的节点:```python
[1, 2]
(1, 2)
{"a": 1}
{1, 2}
```AST 形状:```text
List
elts:
Constant 1
Constant 2
Tuple
elts:
Constant 1
Constant 2
Dict
keys:
Constant "a"
values:
Constant 1
Set
elts:
Constant 1
Constant 2
```编译器稍后可能会折叠一些常量。例如:```python
x = 1 + 2
```可以编译为常量`3`根据优化规则在字节码中。但AST仍然记录优化前的二元运算。
## 20.9 运算符作为 AST 节点
运算符表示为小型 AST 节点对象。
示例:
|来源 | AST 运算符 |
| -------- | ------------ |
|`a + b` | `Add` |
| `a - b` | `Sub` |
| `a * b` | `Mult` |
| `a / b` | `Div` |
| `a // b` | `FloorDiv` |
| `a % b` | `Mod` |
| `a ** b` | `Pow` |
| `a @ b` | `MatMult` |
| `a << b` | `LShift` |
| `a >> b` | `RShift` |
| `a \| b` | `BitOr` |
| `a & b` | `BitAnd` |
| `a ^ b` | `BitXor`|
例子:```python
x = a + b
```谷草转氨酶:```text
BinOp
left: Name "a"
op: Add
right: Name "b"
```该运算符不存储为字符串。它是用结构来表示的。
这使得下游编译器代码更简单。它可以切换运算符节点类型,而不是再次解析文本运算符。
## 20.10 比较
比较使用特殊的 AST 形状,因为 Python 支持链式比较。
例子:```python
a < b < c
```这意味着:```python
a < b and b < c
```除此之外`b`被评估一次。
AST 形状:```text
Compare
left: Name "a"
ops:
Lt
Lt
comparators:
Name "b"
Name "c"
```这与二元运算不同。
二进制表达式如下:```python
a + b + c
```是嵌套的:```text
BinOp
left:
BinOp
left: a
op: Add
right: b
op: Add
right: c
```链式比较表示为一个`Compare`具有多个运算符和比较器的节点。
## 20.11 布尔运算
布尔运算也具有独特的 AST 形状。
例子:```python
a and b and c
```AST 形状:```text
BoolOp
op: And
values:
Name "a"
Name "b"
Name "c"
```相似地:```python
a or b or c
```用途`BoolOp`和`Or`。
这种形状保留了短路结构。编译器发出从左到右计算操作数的字节码,并在知道结果时停止。
布尔运算不是普通的函数调用。他们有特殊的评估规则。
## 20.12 函数定义
函数定义表示为`FunctionDef`。
例子:```python
def add(a: int, b: int = 0) -> int:
return a + b
```AST 形状:```text
FunctionDef
name: "add"
args:
arguments
args:
arg "a", annotation: Name "int"
arg "b", annotation: Name "int"
defaults:
Constant 0
returns:
Name "int"
body:
Return
BinOp
Name "a"
Add
Name "b"
decorator_list:
[]
```函数定义节点存储:```text
function name
argument structure
body statements
decorators
return annotation
type comment
source location
```解析时不执行函数体。它稍后成为函数代码对象的一部分。在运行时,执行`def`语句创建一个函数对象。
## 20.13 参数
函数参数由一个表示`arguments`节点。
它必须支持所有Python参数形式:```python
def f(a, b=1, /, c=2, *args, d, e=3, **kwargs):
pass
```AST 必须区分:```text
positional-only parameters
positional-or-keyword parameters
varargs parameter
keyword-only parameters
keyword-only defaults
kwargs parameter
defaults
annotations
```此结构比简单的名称列表更详细。
编译器稍后使用它来构建具有正确参数计数和标志的代码对象。
## 20.14 类定义
类定义表示为`ClassDef`。
例子:```python
class Point(Base):
x = 0
y = 0
```AST 形状:```text
ClassDef
name: "Point"
bases:
Name "Base"
keywords:
[]
body:
Assign x = 0
Assign y = 0
decorator_list:
[]
```类主体被编译为可执行块。在运行时,CPython 执行类主体以生成命名空间,然后调用元类机制来创建类对象。
AST仅记录类定义结构。它不构建类。
## 20.15 推导式
推导式有专用的 AST 节点。
示例:```python
[x for x in xs]
{x for x in xs}
{k: v for k, v in pairs}
(x for x in xs)
```AST 节点类型:
|来源表格| AST 节点 |
| -------------------- | -------------- |
|列表理解 |`ListComp`|
|集合理解|`SetComp`|
|字典理解 |`DictComp`|
|生成器表达式 |`GeneratorExp`|
每个使用一个或多个`comprehension`节点。
例子:```python
[x * x for x in xs if x > 0]
```AST 形状:```text
ListComp
elt:
BinOp x * x
generators:
comprehension
target: Name "x", Store
iter: Name "xs", Load
ifs:
Compare x > 0
is_async: 0
```推导式是表达式节点,但它们引入了本地绑定行为。稍后的编译器阶段会仔细处理它们的范围。
## 20.16 模式匹配 AST
Python 模式匹配使用专用 AST 节点。
例子:```python
match value:
case 0:
result = "zero"
case [x, y]:
result = x + y
```AST 形状:```text
Match
subject:
Name "value"
cases:
match_case
pattern: MatchValue Constant 0
body:
Assign result = "zero"
match_case
pattern: MatchSequence
MatchAs name="x"
MatchAs name="y"
body:
Assign result = x + y
```模式节点与表达式节点是分开的,因为模式语法具有不同的含义。
例如:```python
case x:
```不加载名为的变量`x`。它将一个值捕获到名称中`x`。
这就是为什么模式匹配不能在任何地方重用普通表达式 AST 节点。它需要特定于模式的结构。
## 20.17 异步 AST 节点
异步语法也出现在 AST 中。
示例:```python
async def fetch():
await client.get(url)
```AST 形状:```text
AsyncFunctionDef
name: "fetch"
body:
Expr
Await
Call
Attribute client.get
```异步循环和上下文管理器也有专用的形式:```python
async for item in stream:
process(item)
async with lock:
run()
```AST 节点:```text
AsyncFor
AsyncWith
Await
AsyncFunctionDef
```这些节点允许稍后的编译器阶段生成协程感知的字节码。
## 20.18 源位置
AST 节点携带源位置。
常见字段包括:```text
lineno
col_offset
end_lineno
end_col_offset
```例子:```python
import ast
tree = ast.parse("x = 1 + 2\n")
assign = tree.body[0]
print(assign.lineno)
print(assign.col_offset)
print(assign.end_lineno)
print(assign.end_col_offset)
```源位置由以下人员使用:```text
tracebacks
syntax errors
debuggers
profilers
coverage tools
AST transformers
editor tooling
```手动生成 AST 时,缺少源位置可能会导致编译错误。帮手`ast.fix_missing_locations()`尽可能填充缺失的位置数据。
例子:```python
import ast
tree = ast.Module(
body=[
ast.Assign(
targets=[ast.Name(id="x", ctx=ast.Store())],
value=ast.Constant(value=1),
)
],
type_ignores=[],
)
tree = ast.fix_missing_locations(tree)
code = compile(tree, "<ast>", "exec")
exec(code)
```## 20.19 AST 验证
在编译 AST 之前,CPython 会对其进行验证。
验证捕获畸形树,例如:```text
Name with no context
assignment target with Load context
expression where statement is required
statement where expression is required
invalid source location fields
invalid operator node placement
missing required fields
```无效 AST 的示例:```python
import ast
tree = ast.Module(
body=[
ast.Assign(
targets=[ast.Name(id="x", ctx=ast.Load())],
value=ast.Constant(value=1),
)
],
type_ignores=[],
)
tree = ast.fix_missing_locations(tree)
compile(tree, "<ast>", "exec")
```目标`x`有`Load`上下文,但赋值需要`Store`。 CPython 拒绝这一点。
这很重要,因为公众`ast`模块允许用户手动构建树。编译器不得假设每个用户创建的 AST 都是有效的。
## 20.20 AST 转换
AST 工具可以在编译之前修改代码。
示例:替换每个整数常量`1`和`42`。```python
import ast
class ReplaceOne(ast.NodeTransformer):
def visit_Constant(self, node):
if node.value == 1:
return ast.copy_location(ast.Constant(value=42), node)
return node
tree = ast.parse("x = 1\n")
tree = ReplaceOne().visit(tree)
tree = ast.fix_missing_locations(tree)
code = compile(tree, "<ast>", "exec")
ns = {}
exec(code, ns)
print(ns["x"])
```这打印:```text
42
```AST 转换功能强大但受到限制。转换后的树必须仍然遵守 AST 有效性规则。
## 20.21 AST 与字节码
AST 和字节码代表不同的级别。
对于这个来源:```python
x = 1 + 2
```谷草转氨酶:```text
Assign
Name "x", Store
BinOp
Constant 1
Add
Constant 2
```字节码可能如下所示:```text
LOAD_CONST 3
STORE_NAME x
RETURN_CONST None
```字节码可能已经包含优化的常量。它还包含执行指令,而不是源语法。
比较:
|层 |代表 |蜜饯 |
| -------- | -------------------- | ------------------------------------------------- |
|代币 |词汇源单位 |公共标记器中的拼写、评论、位置 |
|谷草转氨酶 |语法结构|陈述、表达式、上下文、立场 |
|字节码 |执行计划|堆栈操作、常量、名称、跳转 |
AST 比字节码更接近源,但不如令牌精确。
## 20.22 AST 和符号表
符号表过程读取 AST 以对名称进行分类。
例子:```python
x = 1
def f():
return x
```AST 说:```text
module assigns x
function f loads x
```符号表决定:```text
x is global from inside f
f is local to module scope
```带闭包的示例:```python
def outer():
x = 1
def inner():
return x
return inner
```AST 记录嵌套函数和名称用法。符号表确定:```text
x is local to outer
x is free in inner
x must be stored in a closure cell
```这不是纯粹的解析。它需要对 AST 进行范围分析。
## 20.23 AST 和编译器生成
编译器遍历 AST 并生成字节码。
简化模型:```text
compile Module:
compile each statement
compile Assign:
compile right-hand expression
compile each target in Store context
compile BinOp:
compile left expression
compile right expression
emit binary operation instruction
compile If:
compile test
emit conditional jump
compile body
emit jump over else
compile else body
```编译器是一个具有控制流和堆栈管理逻辑的树遍历器。
这取决于 AST 结构是否正确。格式错误的 AST 可能会导致错误的字节码、崩溃或无效的运行时状态,这就是验证存在的原因。
## 20.24 CPython AST 源文件
重要的 CPython 领域包括:```text
Parser/Python.asdl
Python/Python-ast.c
Python/ast.c
Python/ast_opt.c
Python/symtable.c
Python/compile.c
Lib/ast.py
```ASDL 文件定义 AST 节点架构。生成的 C 代码提供 AST 结构和构造函数。解析器和 AST 代码根据语法匹配构建节点。优化器和编译器使用该树。
概念角色:
|面积 |角色 |
| -------------- | -------------------------------------------------- |
| ASDL 架构 |定义 AST 节点类型和字段 |
|解析器动作 |从语法匹配创建 AST 节点 |
| AST 验证 |拒绝畸形树|
| AST 优化器 |执行树级简化 |
|符号表|分类名称和范围 |
|编译器|从 AST 发出字节码 |
|`Lib/ast.py`| Python 级 AST 助手 |
## 20.25 最小心智模型
使用这个模型:```text
The AST is CPython’s structured syntax tree.
It is built by the parser.
It removes most formatting details.
It records statements, expressions, operators, contexts, and source locations.
It separates syntax structure from later semantic analysis.
The symbol table pass reads the AST to classify names.
The compiler walks the AST to emit bytecode.
```AST 是语法和执行之间的中心交接对象。