21. Bảng ký hiệu
#21. Bảng ký hiệu
Sau khi phân tích cú pháp tạo ra AST, CPython thực hiện phân tích phạm vi.
Giai đoạn này xây dựng các bảng ký hiệu.
Bảng ký hiệu ghi lại cách hoạt động của tên trong từng phạm vi. Nó xác định xem tên là cục bộ, toàn cầu, miễn phí, ô, tham số, được nhập, chú thích hay được tham chiếu từ phạm vi lồng nhau.
Đối với nguồn này:```python x = 10
def outer(): y = 20
def inner():
return x + y
return inner
Giai đoạn bảng ký hiệu xác định:```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
```Phân tích này rất cần thiết vì việc tạo mã byte phụ thuộc vào nó. Việc tải một biến cục bộ sử dụng mã byte khác với việc tải một biến toàn cục hoặc biến đóng.
## 21.1 Vị trí trong Đường dẫn biên dịch
Giai đoạn bảng ký hiệu nằm giữa quá trình xây dựng AST và tạo mã byte.```text
source
↓
tokenization
↓
parsing
↓
AST
↓
symbol table analysis
↓
compiler
↓
code object
↓
bytecode execution
```Trình phân tích cú pháp xây dựng cấu trúc cú pháp.
Giai đoạn bảng ký hiệu xây dựng cấu trúc phạm vi.
Trình biên dịch sau đó sử dụng cả hai.
## 21.2 Bảng ký hiệu chứa đựng những gì
Mỗi phạm vi có một bảng ký hiệu.
Bảng ký hiệu lưu trữ các thông tin như:```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
```Về mặt khái niệm:```text
Scope: function outer
Symbols:
y local + cell
inner local
Scope: function inner
Symbols:
x global
y free
```Giai đoạn bảng ký hiệu không thực thi mã. Nó thực hiện phân loại phạm vi tĩnh.
## 21.3 Python sử dụng phạm vi từ vựng
Python sử dụng phạm vi từ vựng.
Hàm lồng nhau có thể truy cập tên từ phạm vi kèm theo.
Ví dụ:```python
def outer():
x = 10
def inner():
return x
return inner
innercó thể truy cậpxbởi vìxtồn tại trong môi trường từ vựng bao quanh.
Giai đoạn bảng ký hiệu xác định mối quan hệ này trước khi thực thi thời gian chạy.
Nếu không phân tích phạm vi từ vựng, CPython sẽ không biết:```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 có một số loại phạm vi.
| Phạm vi | Ví dụ |
| ------------------- | ----------------- |
| Phạm vi mô-đun | tập tin cấp cao nhất |
| Phạm vi chức năng |`def f():`|
| Phạm vi lớp học |`class C:`|
| Phạm vi hiểu |`[x for x in xs]`|
| Phạm vi máy phát điện |`(x for x in xs)`|
| Phạm vi Lambda |`lambda x: x + 1`|
Mỗi phạm vi có phân tích biểu tượng độc lập.
Ví dụ:```python
x = 1
def f():
x = 2
return x
```Mỗi mô-đun và chức năng đều có cái riêng`x`.
Giai đoạn bảng biểu tượng ngăn cách chúng.
## 21.5 Phạm vi mô-đun
Mã cấp cao nhất thực thi trong phạm vi mô-đun.
Ví dụ:```python
x = 1
y = 2
def add():
return x + y
```Ở phạm vi mô-đun:```text
x global/module-local
y global/module-local
add global/module-local
```Bên trong`add`:
```text
x global
y global
```Trong thời gian chạy, toàn cầu được lưu trữ trong từ điển mô-đun.
Giai đoạn bảng ký hiệu xác định rằng các tham chiếu bên trong`add`nên sử dụng mã byte tra cứu toàn cầu thay vì mã byte tra cứu cục bộ.
## 21.6 Phạm vi chức năng
Các cơ quan chức năng tạo ra phạm vi địa phương.
Ví dụ:```python
def f(a, b):
c = a + b
return c
```Bên trong`f`:
```text
a parameter + local
b parameter + local
c local
```Các tham số được coi là biến cục bộ được khởi tạo.
Trình biên dịch sau đó sẽ phân bổ các vị trí cho các vị trí cục bộ bên trong đối tượng khung.
Giai đoạn bảng ký hiệu xác định:```text
how many local variables exist
which names are parameters
which bytecode instructions to emit
```## 21.7 Biến cục bộ
Tên được gán ở bất kỳ đâu trong hàm sẽ trở thành tên cục bộ trừ khi được khai báo khác.
Ví dụ:```python
x = 10
def f():
print(x)
x = 20
```Điều này làm tăng:```text
UnboundLocalError
```Tại sao?
Bởi vì giai đoạn bảng ký hiệu nhìn thấy sự phân công`x`bên trong`f`:
```python
x = 20
```Điều đó có nghĩa là:```text
x is local to f
```Vì vậy, trước đó:```python
print(x)
```cố gắng đọc địa phương`x`trước khi phân công.
Hành vi này xuất phát từ phân tích phạm vi tĩnh chứ không phải phỏng đoán thời gian chạy.
## 21.8 Tuyên bố toàn cầu`global`thay đổi phân loại phạm vi.
Ví dụ:```python
x = 10
def f():
global x
x = 20
```Bây giờ bảng ký hiệu ghi lại:```text
x global
```bên trong`f`.
Bài tập cập nhật biến cấp độ mô-đun.
Không có`global`, phép gán sẽ tạo ra một biến cục bộ.
Giai đoạn bảng ký hiệu phải xử lý`global`khai báo trước khi biên dịch các quyền truy cập tên.
## 21.9 Tuyên bố phi địa phương`nonlocal`đề cập đến các biến phạm vi chức năng kèm theo.
Ví dụ:```python
def outer():
x = 10
def inner():
nonlocal x
x += 1
inner()
return x
```Bên trong`inner`:
```text
x free variable
```Bên trong`outer`:
```text
x cell variable
```Biến ô là biến cục bộ được nắm bắt bởi phạm vi lồng nhau.
Không có`nonlocal`, bài tập bên trong`inner`sẽ tạo ra một biến cục bộ mới.`nonlocal`nói với bảng ký hiệu:```text
do not create a local binding
use enclosing function binding
```## 21.10 Biến miễn phí
Biến tự do là tên được sử dụng bên trong một phạm vi nhưng được xác định trong phạm vi hàm kèm theo.
Ví dụ:```python
def outer():
x = 10
def inner():
return x
```Bên trong`inner`:
```text
x free variable
xkhông phải là địa phươnginner.
Nó đến từouter.
Các biến miễn phí yêu cầu hỗ trợ đóng.
21.11 Biến ô
Biến ô là biến cục bộ được tham chiếu bởi phạm vi lồng nhau.
Ví dụ:```python def outer(): x = 10
def inner():
return x
```Bên trongouter:
x local + cell
```Tại sao?
Bởi vì`x`phải sống sót sau`outer`trở lại.
Hàm lồng nhau vẫn cần quyền truy cập vào nó.
CPython lưu trữ các biến đã ghi trong các ô đóng được phân bổ theo vùng heap thay vì các ô cục bộ chỉ có ngăn xếp thông thường.
Giai đoạn bảng ký hiệu xác định cục bộ nào cần lưu trữ ô.
## 21.12 Đóng cửa
Việc đóng cửa phụ thuộc trực tiếp vào phân tích bảng ký hiệu.
Ví dụ:```python
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
```Bảng ký hiệu xác định:```text
count in make_counter:
local + cell
count in inc:
free
```Trình biên dịch sau đó tạo ra máy đóng.
Khi chạy:```python
c = make_counter()
```trả về một hàm mang quyền truy cập vào`count`tế bào.
Nếu không có phân tích bảng ký hiệu, việc xây dựng kết thúc sẽ không thể thực hiện được.
## 21.13 Phân giải tên Danh mục
CPython phân loại rộng rãi tên thành các danh mục.
| Danh mục | Ý nghĩa |
| --------------- | -------------------------------- |
| Địa phương | Được xác định trong phạm vi hiện tại |
| Rõ ràng toàn cầu | Khai báo với`global`|
| Toàn cầu tiềm ẩn | Đã giải quyết ở cấp độ mô-đun/nội trang |
| Miễn phí | Đến từ phạm vi kèm theo |
| Tế bào | Địa phương được chụp bởi phạm vi lồng nhau |
Phân loại này kiểm soát việc tạo mã byte.
Ví dụ về danh mục mã byte:
| Danh mục | Họ mã byte |
| ----------- | --------------- |
| Địa phương |`LOAD_FAST`|
| Toàn cầu |`LOAD_GLOBAL`|
| Miễn phí/di động |`LOAD_DEREF`|
| Tra cứu tên |`LOAD_NAME`|
Các quyết định phạm vi khác nhau tạo ra hành vi và hiệu suất thời gian chạy khác nhau.
## 21.14 Mã byte phụ thuộc vào phân tích phạm vi
Ví dụ:```python
def f(a):
return a
```Mã byte:```text
LOAD_FAST a
RETURN_VALUE
alà địa phương.
Hiện nay:```python x = 1
def f():
return x
Mã byte:text
LOAD_GLOBAL x
RETURN_VALUE
Bây giờ trường hợp đóng cửa:python
def outer():
x = 1
def inner():
return x
```Bên tronginner:
LOAD_DEREF x
RETURN_VALUE
```Chỉ riêng trình phân tích cú pháp không thể xác định được các hướng dẫn này.
Giai đoạn bảng ký hiệu cung cấp thông tin phạm vi cần thiết.
## 21.15 Phạm vi lớp học
Các thân lớp có hành vi phạm vi đặc biệt.
Ví dụ:```python
x = 1
class C:
y = x
```Bên trong thân lớp:```text
y local to class namespace
x global
```Các thân lớp thực thi trong từ điển không gian tên của riêng chúng.
Nhưng các phương thức không tự động nắm bắt các biến phạm vi lớp về mặt từ vựng.
Ví dụ:```python
class C:
x = 1
def f(self):
return x
```Điều này không truy cập`C.x`.
Tên`x`bên trong`f`được coi là toàn cục trừ khi được xác định khác.
Phạm vi lớp khác với phạm vi chức năng ở những điểm quan trọng.
## 21.16 Phạm vi hiểu
Sự hiểu biết Python hiện đại tạo ra phạm vi riêng của chúng.
Ví dụ:```python
x = 100
values = [x for x in range(3)]
print(x)
```Đầu ra:```text
100
```Biến hiểu không bị rò rỉ ra phạm vi bên ngoài.
Nội bộ:```text
comprehension creates nested scope
x inside comprehension is local there
outer x remains unchanged
```Các phiên bản Python trước đó hoạt động khác hẳn. CPython hiện đại sử dụng phạm vi hiểu riêng biệt.
## 21.17 Phạm vi biểu thức của trình tạo
Các biểu thức của trình tạo cũng tạo ra các phạm vi lồng nhau.
Ví dụ:```python
x = 10
g = (x for x in range(3))
print(x)
```bên ngoài`x`vẫn không thay đổi.
Biểu thức trình tạo hoạt động tương tự như một hàm lồng nhau ngầm định.
Giai đoạn bảng ký hiệu tạo ra thông tin ký hiệu riêng biệt cho phạm vi trình tạo.
## 21.18 Phạm vi Lambda
Biểu thức Lambda tạo phạm vi hàm.
Ví dụ:```python
x = 10
f = lambda y: x + y
```Bên trong lambda:```text
y local parameter
x free variable
```Lambda là các hàm ẩn danh nhưng việc phân tích bảng ký hiệu xử lý chúng tương tự như các hàm lồng nhau thông thường.
## 21.19 Nhập khẩu và Bảng ký hiệu
Nhập khẩu cũng ảnh hưởng đến bảng ký hiệu.
Ví dụ:```python
import os
from math import sin
```Ký hiệu phạm vi mô-đun:```text
os imported local/global
sin imported local/global
```Chức năng bên trong:```python
def f():
import json
jsontrở thành địa phươngf.
Nhập khẩu là nhiệm vụ từ góc độ phân tích phạm vi.
21.20 Chú thích
Chú thích biến tham gia xử lý ký hiệu.
Ví dụ:python x: int = 1 Bảng ký hiệu ghi lại:text x assigned Chú thích chức năng cũng xuất hiện:```python
def f(x: int) -> str:
return str(x)
## 21.21 Các biến xử lý ngoại lệ
Tên trình xử lý ngoại lệ tạo ra các ràng buộc cục bộ.
Ví dụ:```python
try:
run()
except ValueError as e:
print(e)
```Bên trong khối ngoại lệ:```text
e local
```CPython sau đó sẽ xóa các biến ngoại lệ sau trình xử lý để tránh các chu kỳ tham chiếu liên quan đến truy nguyên.
Giai đoạn bảng ký hiệu xác định chính ràng buộc đó. Hành vi dọn dẹp xảy ra sau đó.
## 21.22 Ràng buộc khớp mẫu
Khớp mẫu giới thiệu các quy tắc ràng buộc.
Ví dụ:```python
match value:
case [x, y]:
print(x, y)
```Các biến mẫu trở thành các ràng buộc cục bộ.
Giai đoạn bảng ký hiệu phải phân biệt:```text
pattern capture
ordinary expression
```bởi vì cú pháp mẫu có ngữ nghĩa khác nhau.
## 21.23 Truyền tải AST trong quá trình phân tích biểu tượng
Giai đoạn bảng biểu tượng đi theo AST.
Mô hình đơn giản hóa:```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
```Việc truyền tải thu thập thông tin trước tiên, sau đó giải quyết các mối quan hệ phạm vi.
## 21.24 Bản chất của phân tích phạm vi hai lần
Phân tích phạm vi không hoàn toàn mang tính cục bộ.
Ví dụ:```python
def f():
print(x)
x = 1
```Ý nghĩa của điều đầu tiên`x`phụ thuộc vào sự phân công sau này.
Do đó CPython không thể phân loại tên theo một lượt từ trái sang phải.
Giai đoạn bảng ký hiệu thu thập thông tin phạm vi một cách hiệu quả trên toàn bộ khối trước khi phân loại cuối cùng.
## 21.25 Cây phạm vi lồng nhau
Phạm vi tạo thành một cây.
Ví dụ:```python
x = 0
def outer():
y = 1
def inner():
z = 2
```Cây phạm vi:```text
Module
outer
inner
```Mỗi phạm vi lưu trữ các liên kết đến phạm vi con.
Độ phân giải có thể thay đổi tự do hướng ra ngoài thông qua các phạm vi kèm theo.
## 21.26 Nội dung
Nếu một tên không được mô-đun cục bộ hay toàn cục xác định, thì việc tra cứu thời gian chạy có thể quay lại nội dung.
Ví dụ:```python
print(len([1, 2, 3]))
```Phạm vi mô-đun bên trong:```text
print implicit global/builtin
len implicit global/builtin
```Giai đoạn bảng ký hiệu không giải quyết được các đối tượng dựng sẵn thực tế. Nó phân loại phong cách tra cứu.
Khi chạy, tra cứu toàn cầu sẽ kiểm tra:```text
module globals
then builtins
```## 21.27`symtable`mô-đun
Python hiển thị các bảng ký hiệu thông qua`symtable`mô-đun.
Ví dụ:```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())
```Các bảng ký hiệu lồng nhau có thể được kiểm tra theo chương trình.
Mô-đun này hữu ích cho:```text
linters
static analyzers
teaching scope behavior
compiler experiments
```## 21.28 Cấu trúc dữ liệu bảng biểu tượng trong CPython
Các khu vực nguồn quan trọng bao gồm:```text
Python/symtable.c
Include/internal/
Python/compile.c
```Các bảng biểu tượng cửa hàng máy móc:```text
scope type
symbol flags
child scopes
free variables
cell variables
parameter information
optimization flags
```Cờ biểu tượng mô tả hành vi như:```text
assigned
used
parameter
global
nonlocal
free
cell
imported
annotated
```Trình biên dịch sau đó sẽ sử dụng siêu dữ liệu này.
## 21.29 Tối ưu hóa và cục bộ
Phân tích phạm vi cho phép truy cập biến cục bộ nhanh chóng.
Các biến cục bộ sử dụng các vị trí được lập chỉ mục bên trong các khung.
Ví dụ:```python
def f(a, b):
c = a + b
return c
```Các nội dung cục bộ có thể được biên dịch thành các truy cập kiểu mảng:```text
locals[0] -> a
locals[1] -> b
locals[2] -> c
```Việc này nhanh hơn nhiều so với việc tra cứu từ điển.
Nếu không có phân tích bảng ký hiệu, CPython không thể tính toán trước bố cục cục bộ.
## 21.30 Mô hình tinh thần tối thiểu
Sử dụng mô hình này:```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.
```Giai đoạn bảng ký hiệu là nơi CPython biến cú pháp thành ngữ nghĩa phạm vi thực thi được.