6. Từ mã nguồn đến thực thi
6. Từ mã nguồn đến thực thi
CPython không thực thi trực tiếp văn bản nguồn Python. Nó biến đổi văn bản nguồn thông qua một số biểu diễn bên trong trước khi lệnh mã byte đầu tiên chạy.
Con đường là:```text source text ↓ tokens ↓ parse tree ↓ abstract syntax tree ↓ symbol table ↓ code object ↓ frame ↓ bytecode evaluation ↓ object operations
## 6.1 Văn bản nguồn
Đầu vào bắt đầu dưới dạng văn bản.```python
x = 1 + 2
print(x)
```Trước khi CPython có thể thực hiện điều này, nó phải biết:```text
where statements begin and end
which characters form names
which characters form numbers
which indentation levels define blocks
which tokens form expressions
which names are local or global
which bytecode instructions are needed
```Mã nguồn Python không chỉ là một chuỗi. Nó có mã hóa, cấu trúc dòng, thụt lề, nhận xét, quy tắc chuỗi ký tự và quy tắc cú pháp.
Giai đoạn đầu tiên chuyển đổi văn bản thô thành mã thông báo.
## 6.2 Mã thông báo
Trình mã thông báo đọc các ký tự nguồn và tạo mã thông báo.
Đối với mã này:```python
x = 1 + 2
```trình mã thông báo tạo ra một luồng tương tự như:```text
NAME("x")
EQUAL("=")
NUMBER("1")
PLUS("+")
NUMBER("2")
NEWLINE
ENDMARKER
```Đối với cấu trúc khối, thụt lề cũng trở thành mã thông báo.```python
if ok:
run()
else:
stop()
```Về mặt khái niệm:```text
NAME("if")
NAME("ok")
COLON
NEWLINE
INDENT
NAME("run")
LPAR
RPAR
NEWLINE
DEDENT
NAME("else")
COLON
NEWLINE
INDENT
NAME("stop")
LPAR
RPAR
NEWLINE
DEDENT
ENDMARKER
```Điều này rất quan trọng. Cấu trúc khối Python không được suy ra sau này từ khoảng trắng. Trình mã thông báo phát ra rõ ràng`INDENT`Và`DEDENT`mã thông báo.
## 6.3 Phân tích cú pháp
Trình phân tích cú pháp sử dụng mã thông báo và kiểm tra xem chúng có tạo thành cú pháp Python hợp lệ hay không.
Vì:```python
x = 1 + 2
```trình phân tích cú pháp nhận ra câu lệnh gán có phía bên phải là biểu thức nhị phân.
Vì:```python
def add(a, b):
return a + b
```trình phân tích cú pháp nhận ra:```text
function definition
function name
parameter list
function body
return statement
binary expression
```Trình phân tích cú pháp từ chối các chuỗi mã thông báo không hợp lệ:```python
x = + * 3
```Điều này đến được trình phân tích cú pháp, nhưng nó không thể rút gọn thành cú pháp hợp lệ.
Đầu ra của trình phân tích cú pháp là một biểu diễn có cấu trúc của chương trình. CPython sau đó chuyển đổi cấu trúc đó thành AST.
## 6.4 Cây cú pháp trừu tượng
AST đại diện cho cấu trúc ngữ nghĩa của chương trình.
Vì:```python
x = 1 + 2
```AST về mặt khái niệm là:```text
Module
Assign
target: Name("x", Store)
value:
BinOp
left: Constant(1)
op: Add
right: Constant(2)
```AST loại bỏ nhiều chi tiết bề mặt và giữ lại cấu trúc cần thiết cho các giai đoạn biên dịch sau này.
Bạn có thể kiểm tra AST từ Python:```python
import ast
tree = ast.parse("x = 1 + 2")
print(ast.dump(tree, indent=4))
```Hình dạng đầu ra ví dụ:```text
Module(
body=[
Assign(
targets=[
Name(id='x', ctx=Store())],
value=BinOp(
left=Constant(value=1),
op=Add(),
right=Constant(value=2)))],
type_ignores=[])
```AST cho biết ý nghĩa của chương trình về mặt cấu trúc. Nó vẫn chưa cho biết hướng dẫn mã byte nào sẽ phát ra.
## 6.5 Bối cảnh tên
Tên AST mang theo ngữ cảnh.
Trong mã này:```python
x = x + 1
```hai công dụng của`x`có những vai trò khác nhau.```text
left side x Store
right side x Load
```Về mặt khái niệm:```text
Assign
target: Name("x", Store)
value:
BinOp
left: Name("x", Load)
op: Add
right: Constant(1)
```Sự khác biệt này quan trọng vì việc tải tên và lưu trữ tên sẽ biên dịch thành các hoạt động khác nhau.```text
Load context read a value
Store context assign a value
Del context delete a binding
```Trình biên dịch dựa vào thông tin này khi phát ra mã byte.
## 6.6 Phân tích bảng ký hiệu
Trước khi tạo mã byte, CPython phân tích tên.
Nó quyết định xem mỗi tên có phải là:```text
local
global
nonlocal
free
cell
implicit builtin lookup
```Ví dụ:```python
x = 10
def f(y):
return x + y
```Bên trong`f`:
```text
y is local
x is global or builtin lookup
```Một ví dụ khác:```python
def outer():
x = 10
def inner():
return x
return inner
```Đây:```text
x is local in outer
x is free in inner
x becomes a cell variable in outer
```Biến ô là biến cục bộ phải tồn tại vì hàm bên trong nắm bắt nó. Biến tự do là biến được hàm sử dụng nhưng được lưu trữ trong phạm vi kèm theo.
Giai đoạn này là cần thiết cho việc đóng cửa.
## 6.7 Đối tượng mã
Sau khi phân tích cú pháp và ký hiệu, CPython biên dịch mã thành các đối tượng mã.
Một đối tượng mã chứa:```text
bytecode
constants
names
local variable names
free variable names
cell variable names
stack size
flags
filename
function name
line mapping information
exception table
```Bạn có thể kiểm tra một đối tượng mã:```python
def add(a, b):
return a + b
code = add.__code__
print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)
print(code.co_freevars)
print(code.co_cellvars)
```Đối tượng mã là bất biến. Nó mô tả mã thực thi nhưng không chứa các giá trị thời gian chạy hiện tại của các biến cục bộ.
## 6,8 Mã byte
Bytecode là định dạng hướng dẫn của CPython.
Vì:```python
def add(a, b):
return a + b
```việc tháo gỡ có thể trông giống như:```text
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```Tên và bố cục mã byte thực tế khác nhau tùy theo phiên bản Python. Ý tưởng cốt lõi vẫn là: hướng dẫn mã byte hoạt động trên một khung.
Sử dụng`dis`:
```python
import dis
def add(a, b):
return a + b
dis.dis(add)
```Mã byte ở cấp độ thấp hơn AST. Nó gần đến việc thực hiện.
AST nói:```text
return a + b
```Mã byte nói:```text
load a
load b
perform addition
return result
```## 6.9 Hằng số và tên
Đối tượng mã lưu trữ các hằng số và tên riêng biệt với mã byte.
Vì:```python
x = 10
print(x)
```hằng số`10`được lưu trữ trong bảng hằng số. Những cái tên`x`Và`print`được lưu trữ trong bảng tên.
Về mặt khái niệm:```text
co_consts = (10, None)
co_names = ("x", "print")
```Mã byte sau đó sẽ tham chiếu các bảng này theo chỉ mục.```text
LOAD_CONST 0 load constant 10
STORE_NAME 0 store into name x
LOAD_NAME 1 load name print
LOAD_NAME 0 load name x
CALL 1
POP_TOP
LOAD_CONST 1 load None
RETURN_VALUE
```Điều này làm cho mã byte trở nên nhỏ gọn. Hướng dẫn lưu trữ các chỉ mục nhỏ thay vì chuỗi hoặc đối tượng đầy đủ.
## 6.10 Thực thi mô-đun
Một tệp Python được biên dịch thành một đối tượng mã cấp mô-đun.
Vì:```python
# demo.py
x = 1
def f():
return x
```CPython biên dịch toàn bộ tệp thành một đối tượng mã. Việc thực thi đối tượng mã đó sẽ tạo ra các ràng buộc mô-đun.
Về mặt khái niệm:```text
create module object
create module dictionary
execute module code object in that dictionary
bind x
create function object f
bind f
```Phần thân hàm cũng được biên dịch thành đối tượng mã riêng của nó. Đối tượng mã mô-đun chứa đối tượng mã chức năng đó dưới dạng hằng số.
Điều này giải thích tại sao việc xác định hàm sẽ thực thi mã tại thời điểm nhập mô-đun: phần thân không chạy nhưng đối tượng hàm được tạo và liên kết.
## 6.11 Định nghĩa hàm
Một định nghĩa hàm là mã thực thi được.
Vì:```python
def add(a, b):
return a + b
```CPython không chạy phần thân ngay lập tức. Nó tạo ra một đối tượng chức năng.
Về mặt khái niệm:```text
load code object for add
load function name
create function object
store function object in current namespace
```Đối tượng hàm chứa:```text
code object
globals dictionary
default values
closure cells
annotations
metadata
```Sau đó, khi hàm được gọi, CPython sẽ tạo một khung từ đối tượng hàm đó và thực thi đối tượng mã của hàm.
## 6.12 Tạo khung
Một khung được tạo khi CPython thực thi một đối tượng mã.
Đối với một cuộc gọi chức năng:```python
add(2, 3)
```CPython tạo khung với:```text
code object for add
globals from add.__globals__
builtins
local slots
argument values
value stack
instruction pointer
exception state
```Các đối số được đặt vào các vị trí biến cục bộ.```text
a = 2
b = 3
```Sau đó, trình đánh giá mã byte bắt đầu thực thi khung.
## 6.13 Ngăn xếp đánh giá
Hầu hết các hướng dẫn mã byte đều giao tiếp thông qua ngăn xếp giá trị của khung.
Vì:```python
return a + b
```việc thực hiện là:```text
LOAD_FAST a push value of a
LOAD_FAST b push value of b
BINARY_OP + pop two values, add, push result
RETURN_VALUE pop result and return it
```Các biến cục bộ được lưu trữ riêng biệt với các giá trị ngăn xếp tạm thời.```text
locals:
a = 2
b = 3
stack:
temporary values used by bytecode
```Đây là lý do tại sao CPython được gọi là máy ảo dựa trên ngăn xếp.
## 6.14 Thao tác đối tượng
Các hướng dẫn mã byte hoạt động trên các đối tượng Python, không phải các nguyên hàm C thô.
Khi CPython thực thi:```python
a + b
```nó không cho rằng`a`Và`b`là số nguyên của máy.
Họ có thể là:```text
integers
floats
strings
lists
tuples
NumPy arrays
user-defined objects
```Hoạt động gửi đi thông qua giao thức đối tượng.
Vì`int + int`, CPython sử dụng phép cộng số nguyên. Vì`str + str`, nó sử dụng nối chuỗi. Đối với các lớp do người dùng định nghĩa, nó có thể gọi`__add__`.
Về mặt khái niệm:```text
BINARY_OP +
inspect operand types
find numeric operation
call appropriate slot
return Python object
```Đây là lý do tại sao mã byte vẫn chung chung trong khi các loại cung cấp hành vi cụ thể.
## 6.15 Truy cập thuộc tính
Dành cho:```python
obj.name
```CPython biên dịch tải thuộc tính.
Về mặt khái niệm:```text
LOAD_FAST obj
LOAD_ATTR name
```Vào thời gian chạy,`LOAD_ATTR`thực hiện các quy tắc tra cứu thuộc tính Python:```text
check type descriptors
check instance dictionary
check non-data descriptors and class attributes
possibly call __getattr__
raise AttributeError if missing
```Truy cập thuộc tính không phải là tra cứu trường thô trong trường hợp chung. Đó là một hoạt động giao thức.
Điều này giải thích tại sao quyền truy cập thuộc tính có thể chạy mã Python.```python
class C:
@property
def name(self):
print("computed")
return 42
obj = C()
obj.name
```Thuộc tính đọc mã mô tả cuộc gọi.
## 6.16 Cuộc gọi
Dành cho:```python
result = f(2, 3)
```CPython đánh giá đối số và đối số có thể gọi được, sau đó thực hiện cuộc gọi.
Về mặt khái niệm:```text
load f
load 2
load 3
call with 2 positional arguments
store result
```Trong thời gian chạy, có thể gọi được:```text
Python function
built-in C function
bound method
class object
object with __call__
partial object
method descriptor
```Lệnh gọi hàm Python sẽ tạo một khung mới. Lệnh gọi tích hợp C gọi trình bao bọc hàm C. Một cuộc gọi lớp phân bổ và khởi tạo một thể hiện.
Hướng dẫn mã byte là chung chung. Công văn thời gian chạy quyết định đường dẫn cuộc gọi chính xác.
## 6.17 Luồng điều khiển
Luồng điều khiển được biên dịch thành các bước nhảy.
Vì:```python
if x:
a()
else:
b()
```CPython phát ra mã byte có hình dạng như:```text
load x
jump if false to else
call a
jump to end
else:
call b
end:
```Đối với các vòng lặp biên dịch thành các hoạt động của giao thức iterator cộng với các bước nhảy.```python
for x in items:
use(x)
```Về mặt khái niệm:```text
get iterator
loop_start:
get next item
if exhausted, jump to loop_end
store x
call use(x)
jump to loop_start
loop_end:
```Tính năng ngôn ngữ ở mức cao. Mô hình thực thi là nhảy mã byte và gọi giao thức.
## 6.18 Xử lý ngoại lệ
Xử lý ngoại lệ biên dịch thành phạm vi mã byte được bảo vệ và siêu dữ liệu trình xử lý.
Vì:```python
try:
risky()
except ValueError:
recover()
```CPython cần biết:```text
which bytecode range is protected
where the handler starts
which exception type to match
how to unwind the stack
where execution continues
```Khi một ngoại lệ xảy ra, người đánh giá sẽ tham khảo siêu dữ liệu xử lý ngoại lệ và chuyển quyền kiểm soát sang trình xử lý thích hợp nếu phù hợp.
Nếu không có trình xử lý nào khớp với khung hiện tại, ngoại lệ sẽ truyền tới người gọi.
## 6.19 Nhập khẩu
Câu lệnh nhập là mã thực thi.```python
import math
```Trong thời gian chạy, CPython sử dụng máy nhập để:```text
check sys.modules
find a module spec
load or create the module
execute module code if needed
bind the name
```Hệ thống nhập được triển khai một phần bằng Python thông qua`importlib`và được hỗ trợ một phần bởi mã thời gian chạy C.
Một tệp mô-đun được biên dịch và thực thi giống như các mã Python khác, nhưng không gian tên thực thi của nó là từ điển mô-đun.
## 6.20 Hiểu biết
Một sự hiểu biết có phạm vi thực hiện riêng của nó.
Vì:```python
squares = [x * x for x in range(10)]
```CPython tạo mã cho phần nội dung hiểu.
Về mặt khái niệm:```text
call range(10)
get iterator
create result list
run comprehension code
append each computed value
store final list in squares
```Biến vòng lặp`x`thuộc phạm vi bên trong của sự hiểu biết, không phải phạm vi chức năng xung quanh.
Đây là lý do tại sao:```python
[x for x in range(3)]
print(x)
```không ràng buộc`x`trong phạm vi xung quanh trong Python hiện đại.
## 6.21 Đóng cửa
Đóng cửa yêu cầu các tế bào.
Vì:```python
def outer():
x = 10
def inner():
return x
return inner
innersử dụng một biến từouter.
CPython không thể lưu trữxnhư một địa phương nhanh bình thường sẽ biến mất khioutertrở lại. Nó lưu trữxtrong một đối tượng ô.
Về mặt khái niệm:text outer local x becomes cell variable inner references x as free variable inner function stores reference to the cell cell keeps x alive after outer returns Đây là lý do tại sao hàm trả về vẫn hoạt động:```python
f = outer()
print(f())
## 6.22 Máy phát điện
Hàm tạo sẽ biên dịch khác với hàm thông thường.```python
def count():
yield 1
yield 2
```Đang gọi`count()`tạo ra một đối tượng máy phát điện. Nó không ngay lập tức chạy cơ thể.
Đối tượng trình tạo lưu trữ trạng thái thực thi bị treo:```text
code object
frame or frame-like execution state
instruction offset
local variables
value stack state
running status
```Mỗi`next()`tiếp tục thực hiện cho đến lần tiếp theo`yield`.
```python
g = count()
next(g)
next(g)
```MỘT`yield`không chỉ là sự trở lại. Nó tạm dừng khung và duy trì trạng thái thực thi.
## 6.23 Coroutine
Một coroutine tương tự như một máy phát điện, nhưng nó tham gia vào quá trình`await`giao thức.```python
async def fetch():
value = await operation()
return value
```Đang gọi`fetch()`tạo ra một đối tượng coroutine. Phần thân chỉ chạy khi coroutine được chờ đợi hoặc lên lịch.
Coroutine lưu trữ trạng thái thực thi bị đình chỉ và tiếp tục lại trong khoảng thời gian đó`await`điểm.
Về mặt khái niệm:```text
create coroutine object
start execution
reach await
suspend coroutine
resume later with result
continue execution
return final value
```Vòng lặp sự kiện nằm ngoài mô hình mã byte cốt lõi, nhưng việc tạm dừng và tiếp tục coroutine là các tính năng thời gian chạy được triển khai bởi các đối tượng và khung CPython.
## 6.24 Định nghĩa lớp
Một câu lệnh lớp là mã thực thi được.```python
class C:
x = 1
def f(self):
return self.x
```CPython không chỉ phân bổ một kiểu tĩnh. Nó thực thi phần thân lớp trong một không gian tên tạm thời.
Về mặt khái niệm:```text
load class name
prepare class namespace
execute class body code object
collect attributes and methods
call metaclass
bind resulting class object to name C
```Điều này giải thích tại sao thân lớp có thể chứa mã tùy ý:```python
class C:
print("building class")
x = 1 + 2
```Thân lớp thực thi ngay lập tức khi câu lệnh lớp chạy.
## 6.25 Ví dụ từ đầu đến cuối
Hãy xem xét:```python
x = 10
def add(y):
return x + y
print(add(5))
```Đường ống là:```text
tokenize source
parse tokens
build AST
analyze symbols
compile module code object
execute module frame
bind x = 10
create function object add
bind add
load print
load add
load 5
call add
create function frame
bind y = 5
load global x
load local y
add objects
return 15
call print
finish module execution
```Điểm quan trọng là CPython đã thực hiện được công việc đáng kể trước khi dòng đầu tiên xuất hiện để chạy.
## 6.26 Nơi mỗi giai đoạn tồn tại
Một bản đồ nguồn hữu ích:```text
Tokenizer Parser/
Parser Parser/ and Grammar/
AST support Python/ast.c
Symbol table Python/symtable.c
Compiler Python/compile.c
Code objects Objects/codeobject.c
Function objects Objects/funcobject.c
Frames Python/ and Objects/frameobject.c
Evaluation loop Python/ceval.c and generated/interpreter files
Objects Objects/
Imports Lib/importlib/ and Python/import.c
```Tên tệp chính xác thay đổi theo thời gian, nhưng bản đồ này đủ ổn định để đọc kho lưu trữ.
## 6.27 Mô hình tinh thần
Giữ mô hình nhỏ gọn này:```text
Source code becomes tokens.
Tokens become syntax.
Syntax becomes AST.
AST plus scope analysis becomes bytecode.
Bytecode lives in code objects.
Code objects execute inside frames.
Frames use local slots and a value stack.
Bytecode instructions operate on PyObject references.
Types decide concrete behavior.
```Toàn bộ hệ thống rất lớn, nhưng trình tự này là xương sống.
## 6.28 Tóm tắt chương
Việc thực thi CPython là một đường dẫn. Nó bắt đầu bằng văn bản nguồn và kết thúc bằng các thao tác đối tượng bên trong bộ đánh giá mã byte. Trình mã thông báo xử lý các ký tự. Trình phân tích cú pháp xử lý cú pháp. AST đại diện cho cấu trúc. Bảng ký hiệu phân loại tên. Trình biên dịch phát ra các đối tượng mã. Các khung thực thi các đối tượng mã. Bytecode thao tác các đối tượng Python thông qua hành vi được xác định theo kiểu.
Quy trình này giải thích cách các cấu trúc Python cấp cao trở thành các hành động thời gian chạy cụ thể.