20. AST
20. AST
Cây cú pháp trừu tượng, thường được gọi là AST, là cách biểu diễn có cấu trúc của mã nguồn Python sau khi phân tích cú pháp.
Trình mã thông báo tạo ra một dòng mã thông báo phẳng. Trình phân tích cú pháp nhận ra ngữ pháp. AST ghi lại kết quả dưới dạng cây câu lệnh và biểu thức.
Đối với nguồn này:python x = 1 + 2 AST có hình dạng như thế này:```text
Module
Assign
targets:
Name id="x", ctx=Store
value:
BinOp
left: Constant value=1
op: Add
right: Constant value=2
## 20.1 Vị trí trong Đường dẫn biên dịch
AST nằm giữa phân tích cú pháp và phân tích trình biên dịch.```text
source bytes
↓
tokenization
↓
parsing
↓
AST
↓
AST validation
↓
symbol table
↓
compiler
↓
code object
↓
bytecode execution
```Trình phân tích cú pháp xây dựng AST. Các giai đoạn sau tiêu thụ nó.
AST trả lời các câu hỏi như:```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?
```Nó không trả lời những câu hỏi như:```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?
```Chúng thuộc về các giai đoạn biên dịch sau này.
## 20.2 Cây cú pháp cụ thể và Cây cú pháp trừu tượng
Trình phân tích cú pháp có thể tạo ra cây cú pháp cụ thể hoặc cây cú pháp trừu tượng.
Cây cú pháp cụ thể bảo tồn chi tiết ngữ pháp. Nó đại diện chặt chẽ cho nguồn.
Cây cú pháp trừu tượng bảo tồn cấu trúc ngữ nghĩa. Nó loại bỏ các chi tiết mà giai đoạn biên dịch sau này không cần.
Ví dụ:```python
x = (((1)))
```Cú pháp cụ thể chứa ba cặp dấu ngoặc đơn.
AST có thể biểu diễn điều này một cách đơn giản như sau:```text
Assign
target: Name "x"
value: Constant 1
```Các dấu ngoặc đơn ảnh hưởng đến việc phân tích cú pháp nhưng chúng không tự tạo ra hành vi thời gian chạy.
Tương tự:```python
x = 1 + 2
```AST giữ phần bổ sung vì nó ảnh hưởng đến hành vi thời gian chạy.```text
BinOp
left: Constant 1
op: Add
right: Constant 2
```Do đó, AST mang tính trừu tượng theo nghĩa trình biên dịch thực tế. Nó giữ các hoạt động, không phải tất cả chính tả.
## 20.3 Truy cập AST cấp Python
Python thể hiện chức năng AST thông qua`ast`mô-đun.
Ví dụ:```python
import ast
tree = ast.parse("x = 1 + 2\n")
print(ast.dump(tree, indent=4))
```Đầu ra điển hình:```text
Module(
body=[
Assign(
targets=[
Name(id='x', ctx=Store())],
value=BinOp(
left=Constant(value=1),
op=Add(),
right=Constant(value=2)))],
type_ignores=[])
```Đây là cấu trúc chung mà CPython sử dụng nội bộ, được hiển thị dưới dạng đối tượng Python.
các`ast`mô-đun hữu ích cho:```text
static analysis
code generation
linting
refactoring
macro-like source transforms
security review tools
documentation tools
teaching compiler structure
```Nhưng API AST công khai vẫn còn là một sự trừu tượng. Biểu diễn C nội bộ của CPython và các lớp AST Python công khai có liên quan với nhau, nhưng chúng không phải là cùng một đối tượng vật lý.
## 20.4 Nút mô-đun
Thư mục gốc của một tệp Python bình thường là một`Module`.
Ví dụ:```python
x = 1
print(x)
```Hình dạng AST:```text
Module
body:
Assign
Expr
type_ignores:
[]
```các`body`là một danh sách có thứ tự các câu lệnh.
Ở cấp mô-đun, trình biên dịch phát ra mã byte thực thi các câu lệnh từ trên xuống dưới.
Có nhiều loại nút gốc khác nhau tùy thuộc vào chế độ phân tích cú pháp:
| Chế độ phân tích | Nút gốc | Ví dụ |
| ------------------ | -------------- | -------------------- |
|`exec` | `Module` | `x = 1` |
| `eval` | `Expression` | `1 + 2` |
| `single` | `Interactive`| Đầu vào REPL |
| chế độ loại chức năng |`FunctionType`| bình luận kiểu kế thừa |
Ví dụ:```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__)
```Chế độ bắt đầu ngữ pháp thay đổi hình dạng AST gốc.
## 20.5 Nút câu lệnh
Tuyên bố thể hiện hành động.
Các nút câu lệnh phổ biến bao gồm:
| Nút | Ví dụ nguồn |
| ------------------ | ------------------------ |
|`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()`như một lời tuyên bố |
|`Pass` | `pass` |
| `Break` | `break` |
| `Continue` | `continue` |
| `Raise` | `raise ValueError()` |
| `Assert` | `assert x` |
| `Delete` | `del x` |
| `Global` | `global x` |
| `Nonlocal` | `nonlocal x`|
Nút câu lệnh xuất hiện ở nơi ngữ pháp mong đợi một câu lệnh. Các nút câu lệnh thường sống trong một`body`, `orelse`, `finalbody`, hoặc danh sách tương tự.
Ví dụ:```python
if x:
a = 1
else:
a = 2
```Hình dạng AST:```text
If
test: Name "x", Load
body:
Assign a = 1
orelse:
Assign a = 2
```các`else`khối được đại diện bởi`orelse`cánh đồng.
## 20.6 Nút biểu thức
Biểu thức tạo ra giá trị.
Các nút biểu thức phổ biến bao gồm:
| Nút | Ví dụ nguồn |
| -------------- | ------------------ |
|`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())`|
Ví dụ:```python
result = obj.method(x, y=2)
```Hình dạng 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 ghi lại mục tiêu cuộc gọi, đối số vị trí, đối số từ khóa và quyền truy cập thuộc tính.
## 20.7 Ngữ cảnh biểu thức
Một số nút biểu thức mang ngữ cảnh.
Các bối cảnh quan trọng nhất là:
| Bối cảnh | Ý nghĩa |
| ------- | -------------------------- |
|`Load`| Đọc một giá trị |
|`Store`| Giao cho một mục tiêu |
|`Del`| Xóa một ràng buộc hoặc mục tiêu |
Ví dụ:```python
x = x + 1
```Hình dạng AST:```text
Assign
target:
Name "x", Store
value:
BinOp
left: Name "x", Load
op: Add
right: Constant 1
```Cách viết giống nhau,`x`, xuất hiện hai lần với ý nghĩa khác nhau.
Ví dụ với các thuộc tính:```python
obj.value = obj.value + 1
```Hình dạng AST:```text
Assign
target:
Attribute obj.value, Store
value:
BinOp
left: Attribute obj.value, Load
op: Add
right: Constant 1
```Bối cảnh là điều cần thiết để tạo mã. Việc tải tên, lưu trữ tên và xóa tên sử dụng các hướng dẫn mã byte khác nhau.
## 20.8 Hằng số
Sử dụng Python AST hiện đại`Constant`cho nhiều giá trị nghĩa đen.
Ví dụ:```python
1
3.14
"hello"
b"bytes"
None
True
False
...
```Mỗi cái có thể xuất hiện dưới dạng:```text
Constant(value=...)
```Các chữ chứa là các nút riêng biệt:```python
[1, 2]
(1, 2)
{"a": 1}
{1, 2}
```Hình dạng 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
```Trình biên dịch sau này có thể gấp một số hằng số. Ví dụ:```python
x = 1 + 2
```có thể biên dịch thành một hằng số`3`bằng mã byte tùy thuộc vào quy tắc tối ưu hóa. Nhưng AST vẫn ghi lại thao tác nhị phân trước khi tối ưu hóa.
## 20.9 Toán tử đóng vai trò là Nút AST
Các toán tử được biểu diễn dưới dạng các đối tượng nút AST nhỏ.
Ví dụ:
| Nguồn | Toán tử 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`|
Ví dụ:```python
x = a + b
```AST:```text
BinOp
left: Name "a"
op: Add
right: Name "b"
```Toán tử không được lưu dưới dạng chuỗi. Nó được thể hiện một cách có cấu trúc.
Điều này làm cho mã trình biên dịch xuôi dòng trở nên đơn giản hơn. Nó có thể bật loại nút toán tử thay vì phân tích lại các toán tử văn bản.
## 20.10 So sánh
Việc so sánh sử dụng hình dạng AST đặc biệt vì Python hỗ trợ so sánh theo chuỗi.
Ví dụ:```python
a < b < c
```Điều này có nghĩa là:```python
a < b and b < c
```ngoại trừ điều đó`b`được đánh giá một lần.
Hình dạng AST:```text
Compare
left: Name "a"
ops:
Lt
Lt
comparators:
Name "b"
Name "c"
```Điều này khác với các hoạt động nhị phân.
Một biểu thức nhị phân như:```python
a + b + c
```được lồng nhau:```text
BinOp
left:
BinOp
left: a
op: Add
right: b
op: Add
right: c
```Một so sánh theo chuỗi được biểu diễn dưới dạng một`Compare`nút có nhiều toán tử và bộ so sánh.
## 20.11 Các phép toán Boolean
Các phép toán Boolean cũng có hình dạng AST riêng biệt.
Ví dụ:```python
a and b and c
```Hình dạng AST:```text
BoolOp
op: And
values:
Name "a"
Name "b"
Name "c"
```Tương tự:```python
a or b or c
```công dụng`BoolOp`với`Or`.
Hình dạng này bảo toàn cấu trúc ngắn mạch. Trình biên dịch phát ra mã byte để đánh giá các toán hạng từ trái sang phải và dừng khi biết kết quả.
Các phép toán Boolean không phải là các lệnh gọi hàm thông thường. Họ có các quy tắc đánh giá đặc biệt.
## 20.12 Định nghĩa hàm
Một định nghĩa hàm được biểu diễn bởi`FunctionDef`.
Ví dụ:```python
def add(a: int, b: int = 0) -> int:
return a + b
```Hình dạng 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:
[]
```Một nút định nghĩa hàm lưu trữ:```text
function name
argument structure
body statements
decorators
return annotation
type comment
source location
```Phần thân hàm không được thực thi trong khi phân tích cú pháp. Sau này nó sẽ trở thành một phần của đối tượng mã của hàm. Trong thời gian chạy, việc thực thi`def`câu lệnh tạo ra một đối tượng hàm.
## 20.13 Đối số
Các đối số của hàm được biểu diễn bằng một`arguments`nút.
Nó phải hỗ trợ tất cả các dạng tham số Python:```python
def f(a, b=1, /, c=2, *args, d, e=3, **kwargs):
pass
```AST phải phân biệt:```text
positional-only parameters
positional-or-keyword parameters
varargs parameter
keyword-only parameters
keyword-only defaults
kwargs parameter
defaults
annotations
```Cấu trúc này chi tiết hơn một danh sách tên đơn giản.
Trình biên dịch sau đó sử dụng nó để xây dựng một đối tượng mã với số lượng đối số và cờ chính xác.
## 20.14 Định nghĩa lớp
Một định nghĩa lớp được biểu diễn bởi`ClassDef`.
Ví dụ:```python
class Point(Base):
x = 0
y = 0
```Hình dạng AST:```text
ClassDef
name: "Point"
bases:
Name "Base"
keywords:
[]
body:
Assign x = 0
Assign y = 0
decorator_list:
[]
```Thân lớp được biên dịch thành một khối thực thi. Trong thời gian chạy, CPython thực thi phần thân lớp để tạo ra một không gian tên, sau đó gọi bộ máy siêu dữ liệu để tạo đối tượng lớp.
AST chỉ ghi lại cấu trúc định nghĩa lớp. Nó không xây dựng lớp học.
## 20.15 Hiểu biết
Sự hiểu biết có các nút AST chuyên dụng.
Ví dụ:```python
[x for x in xs]
{x for x in xs}
{k: v for k, v in pairs}
(x for x in xs)
```Các loại nút AST:
| Mẫu nguồn | Nút AST |
| -------------------- | -------------- |
| Danh sách hiểu |`ListComp`|
| Đặt mức độ hiểu |`SetComp`|
| Hiểu chính tả |`DictComp`|
| Biểu thức máy phát điện |`GeneratorExp`|
Mỗi người sử dụng một hoặc nhiều`comprehension`nút.
Ví dụ:```python
[x * x for x in xs if x > 0]
```Hình dạng AST:```text
ListComp
elt:
BinOp x * x
generators:
comprehension
target: Name "x", Store
iter: Name "xs", Load
ifs:
Compare x > 0
is_async: 0
```Sự hiểu biết là các nút biểu thức, nhưng chúng đưa ra hành vi ràng buộc cục bộ. Các giai đoạn biên dịch sau này xử lý phạm vi của chúng một cách cẩn thận.
## 20.16 Khớp mẫu AST
So khớp mẫu Python sử dụng các nút AST chuyên dụng.
Ví dụ:```python
match value:
case 0:
result = "zero"
case [x, y]:
result = x + y
```Hình dạng 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
```Các nút mẫu tách biệt với các nút biểu thức vì cú pháp mẫu có ý nghĩa khác nhau.
Ví dụ:```python
case x:
```không tải một biến có tên`x`. Nó ghi lại một giá trị vào tên`x`.
Đó là lý do tại sao việc khớp mẫu không thể sử dụng lại các nút AST biểu thức thông thường ở mọi nơi. Nó cần cấu trúc theo mẫu cụ thể.
## 20.17 Nút AST không đồng bộ
Cú pháp không đồng bộ cũng xuất hiện trong AST.
Ví dụ:```python
async def fetch():
await client.get(url)
```Hình dạng AST:```text
AsyncFunctionDef
name: "fetch"
body:
Expr
Await
Call
Attribute client.get
```Các vòng lặp và trình quản lý bối cảnh không đồng bộ cũng có các biểu mẫu chuyên dụng:```python
async for item in stream:
process(item)
async with lock:
run()
```Các nút AST:```text
AsyncFor
AsyncWith
Await
AsyncFunctionDef
```Các nút này cho phép các giai đoạn biên dịch sau này tạo ra mã byte nhận biết coroutine.
## 20.18 Vị trí nguồn
Các nút AST mang vị trí nguồn.
Các trường phổ biến bao gồm:```text
lineno
col_offset
end_lineno
end_col_offset
```Ví dụ:```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)
```Vị trí nguồn được sử dụng bởi:```text
tracebacks
syntax errors
debuggers
profilers
coverage tools
AST transformers
editor tooling
```Khi tạo AST theo cách thủ công, việc thiếu vị trí nguồn có thể gây ra lỗi biên dịch. Người trợ giúp`ast.fix_missing_locations()`điền dữ liệu vị trí còn thiếu nếu có thể.
Ví dụ:```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 Xác thực AST
Trước khi biên dịch AST, CPython xác thực nó.
Xác thực bắt những cây không đúng định dạng như:```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
```Ví dụ về AST không hợp lệ:```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")
```mục tiêu`x`có`Load`bối cảnh, nhưng nhiệm vụ đòi hỏi`Store`. CPython bác bỏ điều này.
Điều này quan trọng bởi vì công chúng`ast`module cho phép người dùng xây dựng cây theo cách thủ công. Trình biên dịch không được cho rằng mọi AST do người dùng tạo đều hợp lệ.
## 20.20 Biến đổi AST
Công cụ AST có thể sửa đổi mã trước khi biên dịch.
Ví dụ: thay thế mọi hằng số nguyên`1`với`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"])
```Bản in này:```text
42
```Chuyển đổi AST rất mạnh mẽ nhưng bị hạn chế. Cây được chuyển đổi vẫn phải tuân theo các quy tắc hợp lệ của AST.
## 20.21 AST so với mã byte
AST và mã byte đại diện cho các cấp độ khác nhau.
Đối với nguồn này:```python
x = 1 + 2
```AST:```text
Assign
Name "x", Store
BinOp
Constant 1
Add
Constant 2
```Mã byte có thể trông giống như:```text
LOAD_CONST 3
STORE_NAME x
RETURN_CONST None
```Mã byte có thể đã chứa các hằng số được tối ưu hóa. Nó cũng chứa các hướng dẫn thực thi, không phải cú pháp nguồn.
So sánh:
| Lớp | Đại diện | Bảo quản |
| -------- | -------------------- | ------------------------------------------------- |
| Mã thông báo | Đơn vị nguồn từ vựng | chính tả, nhận xét trong mã thông báo công khai, vị trí |
| AST | Cấu trúc cú pháp | câu nói, biểu thức, bối cảnh, vị trí |
| Mã byte | Kế hoạch thực hiện | thao tác ngăn xếp, hằng, tên, bước nhảy |
AST gần nguồn hơn mã byte nhưng kém chính xác hơn mã thông báo.
## 20.22 AST và bảng ký hiệu
Thẻ bảng ký hiệu đọc AST để phân loại tên.
Ví dụ:```python
x = 1
def f():
return x
```AST nói:```text
module assigns x
function f loads x
```Bảng ký hiệu quyết định:```text
x is global from inside f
f is local to module scope
```Ví dụ với việc đóng cửa:```python
def outer():
x = 1
def inner():
return x
return inner
```AST ghi lại các hàm lồng nhau và cách sử dụng tên. Bảng ký hiệu xác định rằng:```text
x is local to outer
x is free in inner
x must be stored in a closure cell
```Đây không hoàn toàn là phân tích cú pháp. Nó yêu cầu phân tích phạm vi qua AST.
## 20.23 AST và tạo trình biên dịch
Trình biên dịch xử lý AST và phát ra mã byte.
Mô hình đơn giản hóa:```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
```Trình biên dịch là một trình duyệt dạng cây với logic điều khiển luồng và quản lý ngăn xếp.
Nó phụ thuộc vào cấu trúc AST là chính xác. AST không đúng định dạng có thể gây ra mã byte sai, sự cố hoặc trạng thái thời gian chạy không hợp lệ, đó là lý do tại sao tồn tại xác thực.
## 20.24 Tệp nguồn CPython AST
Các khu vực CPython quan trọng bao gồm:```text
Parser/Python.asdl
Python/Python-ast.c
Python/ast.c
Python/ast_opt.c
Python/symtable.c
Python/compile.c
Lib/ast.py
```Tệp ASDL xác định lược đồ nút AST. Mã C được tạo cung cấp các cấu trúc và hàm tạo AST. Các nút xây dựng mã AST và trình phân tích cú pháp từ các kết quả khớp ngữ pháp. Trình tối ưu hóa và trình biên dịch sử dụng cây.
Vai trò khái niệm:
| Khu vực | Vai trò |
| -------------- | ------------------------------------- |
| Lược đồ ASDL | Xác định các loại và trường nút AST |
| Hành động phân tích cú pháp | Tạo nút AST từ các kết quả khớp ngữ pháp |
| Xác thực AST | Từ chối cây dị hình |
| Trình tối ưu hóa AST | Thực hiện đơn giản hóa cấp cây |
| Bảng ký hiệu | Phân loại tên và phạm vi |
| Trình biên dịch | Phát ra mã byte từ AST |
|`Lib/ast.py`| Người trợ giúp AST cấp Python |
## 20,25 Mô hình tinh thần tối thiểu
Sử dụng mô hình này:```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 là đối tượng chuyển giao trung tâm giữa cú pháp và thực thi.