28. Khung
#28. Khung
Khung là bản ghi thời gian chạy cho một lần thực thi mã Python đang hoạt động. Khi CPython gọi một hàm Python, thực thi phần thân mô-đun, chạy phần thân lớp, tiếp tục trình tạo hoặc tiếp tục một coroutine, nó sẽ sử dụng bản ghi thực thi giống như khung để giữ trạng thái hiện tại.
Một đối tượng mã cho biết những gì cần thực thi.
Một khung cho biết vị trí thực thi hiện tại và giá trị nào hiện đang hoạt động.text code object = immutable instructions and metadata frame = mutable execution state for one run of that code Đối với chức năng này:```python
def add(a, b):
c = a + b
return c
Một khung được tạo cho`add(2, 3)`chứa các giá trị đối số hiện tại, các vị trí biến cục bộ, giá trị ngăn xếp, vị trí lệnh, trạng thái ngoại lệ và các liên kết đến bối cảnh thực thi.
## 28.1 Tại sao khung tồn tại
Một chương trình Python có thể có nhiều lệnh gọi hoạt động cùng lúc:```python
def a():
return b()
def b():
return c()
def c():
return 42
a()
```Trong quá trình thực thi, CPython cần ghi nhớ từng lệnh gọi bị treo trong khi lệnh gọi chạy.
Về mặt khái niệm:```text
frame for a
waiting for b
frame for b
waiting for c
frame for c
currently executing
```Mỗi khung lưu trữ đủ trạng thái để tiếp tục mã của nó sau khi lệnh gọi lồng nhau quay trở lại.
Một khung trả lời những câu hỏi sau:```text
Which code object is running?
Which instruction is next?
What are the local variables?
What temporary values are on the stack?
Which globals and builtins are visible?
What exception is being handled?
Is tracing or profiling active?
Who called this frame?
```## 28.2 Đối tượng mã và khung
Một đối tượng mã có thể tái sử dụng được. Một khung là một trường hợp thực thi.```python
def f(x):
return x + 1
a = f(10)
b = f(20)
```Cả hai cuộc gọi đều sử dụng cùng một đối tượng mã:```python
print(f.__code__)
```Nhưng mỗi cuộc gọi có trạng thái khung riêng.
| Khái niệm | Đối tượng mã | Khung |
|---|---|---|
| Khả năng thay đổi | Chủ yếu là bất biến | Có thể thay đổi |
| Trọn đời | Thường tồn tại lâu dài | Thông thường một cuộc gọi hoặc thực hiện bị đình chỉ |
| Chứa mã byte | Có | Đối tượng mã tham chiếu |
| Chứa người dân địa phương | Không | Có |
| Chứa ngăn xếp | Không | Có |
| Chứa con trỏ lệnh | Không | Có |
| Được chia sẻ qua các cuộc gọi | Có | Không |
Đối tượng mã là đoạn chương trình. Khung là quá trình kích hoạt đang chạy của đoạn đó.
## 28.3 Khung chức năng
Lệnh gọi hàm sẽ tạo hoặc khởi tạo một khung cho callee.```python
def square(x):
return x * x
square(9)
```Đối tượng hàm chứa:```text
code object
globals
defaults
keyword defaults
closure cells
annotations
qualname
module name
```Khi được gọi, CPython kết hợp đối tượng hàm với các đối số thực tế và tạo trạng thái thực thi khung.
Về mặt khái niệm:```text
function object
code object
globals
call arguments
x = 9
new frame
code = square.__code__
globals = square.__globals__
locals slot 0 = 9
value stack = empty
instruction pointer = start
```Sau đó, vòng đánh giá sẽ chạy khung cho đến khi nó trả về, tăng, nhường hoặc tạm dừng.
## 28.4 Khung mô-đun
Phần thân mô-đun cũng được thực thi trong một khung.
Đối với một tập tin:```python
# app.py
x = 10
def f():
return x
```CPython biên dịch toàn bộ mô-đun thành một đối tượng mã. Việc chạy tệp sẽ thực thi đối tượng mã đó trong không gian tên mô-đun.
Về mặt khái niệm:```text
module frame
code = compiled app.py
globals = module.__dict__
locals = module.__dict__
```Ở phạm vi mô-đun, toàn cầu và địa phương thường tham chiếu đến cùng một từ điển.
Đây là lý do tại sao phép gán cấp cao nhất được ghi vào không gian tên mô-đun:```python
x = 10
```tạo ra:```text
module.__dict__["x"] = 10
```Việc thực thi chức năng là khác nhau. Chức năng cục bộ sử dụng các khe cục bộ nhanh chứ không phải từ điển mô-đun làm bộ lưu trữ chính.
## Khung thân lớp 28.5
Thân lớp cũng thực thi dưới dạng mã.```python
class User:
kind = "human"
def name(self):
return "anonymous"
```Câu lệnh lớp không chỉ đơn giản khai báo một cấu trúc tĩnh. CPython thực thi phần thân lớp trong một không gian tên tạm thời, sau đó xây dựng đối tượng lớp từ không gian tên đó.
Về mặt khái niệm:```text
prepare class namespace
execute class body frame
collect assignments and function objects
call metaclass to create class object
bind class object to name User
```Trong quá trình thực thi nội dung lớp:```python
kind = "human"
```lưu trữ vào không gian tên lớp.
Hàm lồng nhau:```python
def name(self):
return "anonymous"
```tạo một đối tượng hàm và lưu trữ nó trong không gian tên lớp. Nó không chạy phần thân phương thức.
Sau khi nội dung lớp kết thúc, CPython chuyển không gian tên cho siêu dữ liệu, thường là`type`.
## 28.6 Nội dung khung
Bản ghi thực thi giống khung CPython chứa một số loại dữ liệu.
| Danh mục | Mục đích |
|---|---|
| Mã tham khảo | Đối tượng mã đang được thực thi |
| Toàn cầu | Không gian tên toàn cầu để tra cứu tên |
| Nội dung | Dự phòng không gian tên dựng sẵn |
| Người dân địa phương | Biến cục bộ hoặc không gian tên |
| Người dân địa phương nhanh chóng | Mảng khe cho hàm cục bộ |
| Ngăn xếp giá trị | Toán hạng tạm thời cho mã byte |
| Trạng thái hướng dẫn | Vị trí mã byte hiện tại |
| Trạng thái ngoại lệ | Siêu dữ liệu xử lý ngoại lệ đang hoạt động |
| Quan hệ người gọi | Liên kết tới khung trước đó hoặc ngữ cảnh cuộc gọi |
| Trạng thái truy tìm | Trình gỡ lỗi, trình lược tả, móc bảo hiểm |
| Nhà nước chủ sở hữu | Trình tạo, coroutine hoặc trạng thái cuộc gọi thông thường |
Một cấu trúc đơn giản hóa trông như thế này:```text
frame
code object
previous frame
globals dictionary
builtins dictionary
locals representation
fast local slots
value stack
instruction pointer
exception state
tracing flags
```Cấu trúc C chính xác thay đổi trên các phiên bản CPython. Các trường khái niệm vẫn ổn định.
## 28.7 Người dân địa phương nhanh
Chức năng cục bộ được tối ưu hóa.
Trong chức năng này:```python
def f(a, b):
c = a + b
return c
```Trình biên dịch biết tên địa phương:```text
a
b
c
```Nó gán cho chúng các chỉ mục:
| Tên | Khe |
|---|---:|
|`a`| 0 |
|`b`| 1 |
|`c`| 2 |
Trong quá trình thực thi, CPython có thể truy cập cục bộ theo vị trí:```text
LOAD_FAST 0
LOAD_FAST 1
STORE_FAST 2
LOAD_FAST 2
```Điều này tránh việc tra cứu từ điển cho các hàm cục bộ thông thường.
Khung lưu trữ các giá trị này trong một vùng giống như mảng:```text
fast locals
slot 0: a
slot 1: b
slot 2: c
```Đây là một lý do khiến biến cục bộ nhanh hơn biến toàn cầu.
## 28.8 Từ điển địa phương
Python giới thiệu người dân địa phương thông qua`locals()`và thuộc tính khung.```python
def f(a):
b = a + 1
print(locals())
f(10)
```Đầu ra:```text
{'a': 10, 'b': 11}
```Nhưng bên trong một hàm, bộ lưu trữ thực thi thực tế là cục bộ nhanh. Chế độ xem từ điển có thể được cụ thể hóa hoặc đồng bộ hóa với bộ nhớ trong tùy thuộc vào ngữ cảnh và phiên bản Python.
Sự khác biệt này rất quan trọng:```python
def f():
x = 1
locals()["x"] = 2
return x
```Bạn không nên dựa vào việc sửa đổi từ điển được trả về bởi`locals()`để thay đổi các biến cục bộ được tối ưu hóa bên trong một hàm.
Ở phạm vi mô-đun, tình huống khác:```python
locals()["x"] = 2
print(x)
```Ở cấp độ mô-đun, người dân địa phương là từ điển mô-đun, vì vậy điều này thường hoạt động.
## 28.9 Toàn cầu và Nội dung
Một khung lưu trữ các tham chiếu đến các không gian tên chung và nội trang được sử dụng để phân giải tên.
Vì:```python
def f(xs):
return len(xs)
lenkhông phải là biến cục bộ. CPython sử dụng tra cứu toàn cầu:text look in function globals if missing, look in builtins if missing, raise NameError Khung cung cấp cả hai từ điển:text frame.globals = module namespace frame.builtins = builtins namespace Về mặt khái niệm:python def f(xs): return len(xs) chạy như:```text
LOAD_GLOBAL len
LOAD_FAST xs
CALL 1
RETURN_VALUE
`LOAD_GLOBAL`phụ thuộc vào toàn cầu và nội dung của khung.
## 28.10 Ngăn xếp giá trị
Mỗi khung có một ngăn xếp giá trị được sử dụng để thực thi mã byte.
Vì:```python
def f(a, b, c):
return (a + b) * c
```Ngăn xếp thay đổi gần như thế này:
| Hướng dẫn | Xếp chồng trước | Xếp chồng sau |
|---|---|---|
|`LOAD_FAST a` | `[]` | `[a]` |
| `LOAD_FAST b` | `[a]` | `[a, b]` |
| `BINARY_OP +` | `[a, b]` | `[a + b]` |
| `LOAD_FAST c` | `[a + b]` | `[a + b, c]` |
| `BINARY_OP *` | `[a + b, c]` | `[(a + b) * c]` |
| `RETURN_VALUE` | `[result]`| trở lại |
Ngăn xếp lưu trữ các tham chiếu đối tượng, không phải các giá trị thô chưa được đóng hộp.
Vì vậy, đối với số nguyên, ngăn xếp chứa các con trỏ tới các đối tượng số nguyên Python.```text
value stack
PyObject* -> int object
PyObject* -> int object
```Hướng dẫn mã byte đẩy, bật, thay thế hoặc kiểm tra các mục nhập ngăn xếp này.
## 28.11 Vị trí hướng dẫn
Một khung theo dõi vị trí mã byte hiện tại.
Đối với mã đường thẳng, con trỏ lệnh tiến lên.
Đối với các nhánh, nó nhảy.```python
def abs_like(x):
if x < 0:
return -x
return x
```Luồng điều khiển khái niệm:```text
load x
load 0
compare <
jump if false to return_x
load x
unary negative
return
return_x:
load x
return
```Khung ghi lại hướng dẫn tiếp theo. Vị trí này quan trọng đối với:```text
normal execution
branches
loops
exceptions
tracebacks
line tracing
profiling
debugging
```Tracebacks sử dụng trạng thái khung để báo cáo nơi xảy ra ngoại lệ.
## 28.12 Khung và dấu vết
Khi một ngoại lệ lan truyền, CPython ghi lại thông tin truy nguyên từ các khung.
Ví dụ:```python
def a():
b()
def b():
c()
def c():
1 / 0
a()
```Truy nguyên hiển thị chuỗi cuộc gọi đang hoạt động:```text
a
b
c
ZeroDivisionError
```Mỗi mục truy ngược đề cập đến một khung và vị trí lệnh. Đây là lý do tại sao Python có thể hiển thị tên tệp nguồn, số dòng và tên hàm.
Truy nguyên không chỉ đơn thuần là một chuỗi. Nó là một chuỗi thông tin thời gian chạy có cấu trúc.
## 28.13 Truy cập khung từ Python
Python hiển thị các đối tượng khung thông qua một số API.```python
import inspect
def f():
frame = inspect.currentframe()
print(frame.f_code.co_name)
print(frame.f_locals)
print(frame.f_globals is globals())
f()
```các`frame`đối tượng hiển thị các trường như:
| Thuộc tính | Ý nghĩa |
|---|---|
|`f_code`| Đối tượng mã |
|`f_locals`| Chế độ xem không gian tên cục bộ |
|`f_globals`| Không gian tên toàn cầu |
|`f_builtins`| Không gian tên dựng sẵn |
|`f_back`| Khung trước |
|`f_lineno`| Dòng nguồn hiện tại |
|`f_trace`| Chức năng theo dõi |
Bạn cũng có thể sử dụng:```python
import sys
frame = sys._getframe()
print(frame.f_code.co_name)
```Các API này mạnh mẽ nhưng nhạy cảm với việc triển khai. Giữ cho các đối tượng khung còn tồn tại cũng có thể giữ cho các biến và đối tượng cục bộ còn tồn tại.
## 28.14 Thời gian sử dụng khung hình
Hầu hết các khung chức năng bình thường đều tồn tại trong thời gian ngắn.```python
def f():
x = object()
return 1
f()
```Khi`f`trả về, khung của nó có thể bị hủy hoặc tái sử dụng và các tham chiếu cục bộ của nó có thể được giải phóng.
Nhưng khung có thể tồn tại lâu hơn trong một số trường hợp:```text
tracebacks keep frames alive
generators suspend frames
coroutines suspend frames
debuggers inspect frames
profilers observe frames
closures may keep cells alive
manual references to frames keep them alive
```Ví dụ:```python
import inspect
saved = None
def f():
global saved
x = [1, 2, 3]
saved = inspect.currentframe()
f()
```Toàn cầu`saved`bây giờ đề cập đến khung. Khung đó đề cập đến người dân địa phương của nó. Danh sách được giao cho`x`có thể vẫn còn sống vì khung vẫn còn sống.
Đây là nguyên nhân phổ biến gây ra tình trạng lưu giữ bộ nhớ đáng ngạc nhiên trong các công cụ gỡ lỗi và mã xử lý ngoại lệ.
## 28.15 Chuỗi khung
Một khung có thể tham chiếu người gọi nó thông qua`f_back`.
```python
import inspect
def outer():
inner()
def inner():
frame = inspect.currentframe()
print(frame.f_code.co_name)
print(frame.f_back.f_code.co_name)
outer()
```Về mặt khái niệm:```text
inner frame
f_back -> outer frame
f_back -> module frame
```Chuỗi này cho phép xây dựng truy nguyên và kiểm tra ngăn xếp.
Điều đó cũng có nghĩa là việc giữ khung trong cùng có thể giữ cho khung của người gọi tồn tại.```text
saved inner frame
keeps outer frame
keeps module frame references
```Đối với các công cụ nhạy cảm với bộ nhớ, các tham chiếu khung phải được giải phóng một cách có chủ ý.
## 28.16 Trình tạo và Khung treo
Trình tạo giữ trạng thái thực thi sau khi đạt năng suất.```python
def gen():
x = 1
yield x
x = 2
yield x
```Gọi hàm sẽ tạo một đối tượng trình tạo:```python
g = gen()
```Cơ thể không chạy ngay lập tức. Khi được tiếp tục:```python
next(g)
```khung hình chạy cho đến lần đầu tiên`yield`.
Sau đó`yield`, khung vẫn bị treo:```text
generator object
suspended frame
code object
local x = 1
instruction position after first yield
value stack state
```Cuộc gọi tiếp theo sẽ tiếp tục từ vị trí hướng dẫn đã lưu đó:```python
next(g)
```Điều này khác với khung chức năng thông thường, kết thúc khi trả về.
## 28.17 Coroutine và Frame
Coroutine sử dụng ý tưởng chung giống như trình tạo: trạng thái thực thi có thể tiếp tục.```python
async def fetch():
data = await read()
return data
```Tại`await`, coroutine có thể tạm dừng. Khung của nó giữ lại:```text
local variables
current instruction position
pending awaitable
exception state
return path
```Một vòng lặp sự kiện sau đó sẽ tiếp tục lại nó.```text
coroutine object
suspended frame or frame-like state
locals
stack
instruction pointer
```Do đó, việc thực thi không đồng bộ phụ thuộc vào việc tạm dừng và nối lại khung. Nó không phải là thời gian chạy ngôn ngữ riêng biệt.
## 28.18 Vật chất hóa khung
CPython hiện đại phân biệt các khung thực thi nội bộ với các đối tượng khung hiển thị đầy đủ bằng Python.
Trình thông dịch có thể chạy với các khung bên trong nhỏ gọn để đạt hiệu suất cao. Một đối tượng khung hình đầy đủ chỉ có thể được tạo khi cần bằng cách xem xét nội tâm, theo dõi, gỡ lỗi, xử lý truy nguyên hoặc các API như`sys._getframe()`.
Về mặt khái niệm:```text
internal frame
optimized execution record
Python frame object
object exposed to Python code
wraps or materializes execution state
```Sự khác biệt này cải thiện hiệu suất vì không phải mọi lệnh gọi hàm đều cần một đối tượng khung Python được phân bổ theo đống mà mã người dùng có thể nhìn thấy.
Mô hình khái niệm vẫn giữ nguyên: vẫn có trạng thái thực thi. Sự đại diện chính xác có thể khác nhau.
## 28.19 Khung và Đóng
Việc đóng cửa yêu cầu bộ nhớ đặc biệt cho các biến được chia sẻ giữa các hàm lồng nhau.
Ví dụ:```python
def outer():
x = 10
def inner():
return x
return inner
```Biến`x`phải sống sót sau`outer`trả về vì`inner`vẫn sử dụng nó.
CPython xử lý việc này bằng các đối tượng ô.
Về mặt khái niệm:```text
outer frame
x stored in cell
inner function
closure references same cell
```Sau đó`outer`trả về, khung có thể biến mất, nhưng ô vẫn tồn tại vì hàm trả về tham chiếu đến nó.```python
fn = outer()
print(fn())
```Việc đóng cửa không giữ được toàn bộ`outer`frame còn sống trong trường hợp bình thường. Nó giữ cho các biến ô cần thiết còn tồn tại.
## 28.20 Biến ô và biến miễn phí
Trình biên dịch phân loại các biến đóng.
| Kỳ hạn | Ý nghĩa |
|---|---|
| Biến ô | Biến cục bộ được nắm bắt bởi hàm bên trong |
| Biến miễn phí | Biến được sử dụng ở đây nhưng được xác định ở phạm vi bên ngoài |
Ví dụ:```python
def outer():
x = 1
def inner():
return x
return inner
```Vì`outer`, `x`là một biến ô.
Vì`inner`, `x`là một biến tự do.
Bạn có thể kiểm tra điều này:```python
def outer():
x = 1
def inner():
return x
return inner
print(outer.__code__.co_cellvars)
print(outer().__code__.co_freevars)
```Bố cục khung bao gồm bộ nhớ cho các ô này để các hàm lồng nhau có thể chia sẻ các biến một cách an toàn.
## 28.21 Khung và Ngoại lệ
Khung lưu trữ trạng thái xử lý ngoại lệ.
Vì:```python
def f(x):
try:
return 10 / x
except ZeroDivisionError:
return 0
```Đối tượng mã chứa siêu dữ liệu xử lý ngoại lệ. Khung lưu trữ trạng thái thực thi hiện tại cần thiết để sử dụng siêu dữ liệu đó.
Khi phép chia đưa ra một ngoại lệ, trình thông dịch sẽ sử dụng vị trí hướng dẫn của khung để tìm trình xử lý phù hợp.
Về mặt khái niệm:```text
exception raised
inspect current frame
locate handler for current bytecode range
adjust stack state
jump to handler
```Nếu không có trình xử lý nào tồn tại trong khung hiện tại, khung sẽ thư giãn và ngoại lệ sẽ truyền đến khung người gọi.
## 28.22 Khung và`finally`MỘT`finally`khối cũng phụ thuộc vào trạng thái khung chính xác.```python
def f():
try:
return 1
finally:
cleanup()
```các`finally`khối chạy ngay cả khi hàm đang quay trở lại.
Khung phải nhớ rằng lệnh trả về đang chờ xử lý trong khi nó thực thi mã dọn dẹp.
Về mặt khái niệm:```text
start return with value 1
enter finally block
call cleanup
if cleanup succeeds:
complete original return
if cleanup raises:
replace return with new exception
```Đây là lý do tại sao trạng thái ngoại lệ và trả về là một phần của quá trình thực thi khung chứ không phải là những suy nghĩ đơn giản.
## 28.23 Khung và Truy tìm
Móc truy tìm hoạt động trên khung.```python
import sys
def trace(frame, event, arg):
print(event, frame.f_code.co_name, frame.f_lineno)
return trace
def f(x):
y = x + 1
return y
sys.settrace(trace)
f(10)
sys.settrace(None)
```Hàm theo dõi nhận khung hiện tại. Nó có thể kiểm tra mã, địa phương, toàn cầu, số dòng và mối quan hệ cuộc gọi.
Việc theo dõi rất hữu ích cho:```text
debuggers
coverage tools
teaching tools
profilers
runtime monitors
```Nhưng việc truy tìm có cái giá phải trả. Nó buộc trình thông dịch phải duy trì và hiển thị nhiều trạng thái hơn tại nhiều điểm thực thi hơn.
## 28.24 Khung và Cấu hình
Hồ sơ tương tự nhưng thường thô hơn so với truy tìm.```python
import sys
def profile(frame, event, arg):
print(event, frame.f_code.co_name)
sys.setprofile(profile)
def f():
return 1
f()
sys.setprofile(None)
```Các sự kiện lập hồ sơ bao gồm các cuộc gọi và trả lại. Trình phân tích sử dụng dữ liệu khung để phân bổ thời gian hoặc số lượng cuộc gọi cho các hàm.
Khung cung cấp ánh xạ từ thực thi thời gian chạy đến cấu trúc chương trình cấp nguồn.
## 28.25 Khung và đệ quy
Cuộc gọi đệ quy tạo nhiều khung cho cùng một đối tượng mã.```python
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
```Vì`fact(4)`:
```text
fact(4)
fact(3)
fact(2)
fact(1)
```Mỗi cuộc gọi đều có địa phương riêng`n`.
```text
frame fact: n = 4
frame fact: n = 3
frame fact: n = 2
frame fact: n = 1
```Tất cả các khung đều tham chiếu cùng một đối tượng mã, nhưng các vị trí cục bộ của chúng khác nhau.
CPython theo dõi độ sâu đệ quy để ngăn các cuộc gọi đệ quy không giới hạn làm cạn kiệt tài nguyên cấp thấp hơn.
## 28.26 Khung hình và khả năng lưu giữ bộ nhớ
Khung có thể giữ lại nhiều bộ nhớ hơn dự kiến.
Ví dụ:```python
def load_big():
data = bytearray(100_000_000)
raise RuntimeError("failed")
try:
load_big()
except RuntimeError as exc:
saved = exc
```Ngoại lệ có thể giữ lại dấu vết. Truy nguyên có thể giữ lại khung. Các khung có thể giữ lại các biến cục bộ. Vì thế`data`có thể vẫn còn sống.
Một mô hình dọn dẹp phổ biến là:```python
try:
load_big()
except RuntimeError as exc:
handle(exc)
exc = None
```Hoặc tránh lưu trữ ngoại lệ lâu hơn mức cần thiết.
Chuỗi lưu giữ trông như thế này:```text
exception
traceback
frame
locals
large object
```Hiểu khung giúp giải thích hành vi này.
## 28.27 Các đối tượng khung mang tính nội tâm, không miễn phí
Khung nội tâm rất mạnh mẽ, nhưng nó có chi phí.
Các hoạt động như thế này có thể buộc tạo hoặc đồng bộ hóa đối tượng khung:```python
inspect.currentframe()
sys._getframe()
locals()
traceback inspection
debugger hooks
coverage tracing
```Điều này có thể ảnh hưởng đến:```text
performance
memory lifetime
local variable synchronization
optimizer freedom
debugging visibility
```Đối với mã ứng dụng thông thường, hãy tránh xem xét nội bộ khung trong các đường dẫn nóng. Đối với trình gỡ lỗi, trình lược tả và công cụ thời gian chạy, việc xem xét nội bộ khung là điều cần thiết.
## 28.28 Thực thi khung đơn giản
Một mô hình thực hiện chức năng đơn giản hóa:```c
PyObject *
run_function(PyFunctionObject *func, PyObject **args, int nargs)
{
Frame frame;
frame.code = func->code;
frame.globals = func->globals;
frame.builtins = get_builtins(func->globals);
frame.localsplus[0] = args[0];
frame.localsplus[1] = args[1];
frame.stack_pointer = frame.stack;
frame.instruction_pointer = frame.code->first_instruction;
return eval_frame(&frame);
}
```Điều này bỏ qua nhiều chi tiết thực tế:```text
keyword arguments
defaults
closures
cell variables
free variables
generators
coroutines
exceptions
tracing
profiling
reference ownership
specialization
thread state
recursion checks
```Tuy nhiên, hình dạng vẫn chính xác: lệnh gọi hàm chuẩn bị một khung, sau đó vòng đánh giá sẽ chạy khung đó.
## 28.29 Bố cục khung đơn giản
Bố cục dạy học:```c
typedef struct {
CodeObject *code;
DictObject *globals;
DictObject *builtins;
Object **localsplus;
Object **stack_pointer;
Instruction *instruction_pointer;
ExceptionState exception_state;
struct Frame *previous;
} Frame;
```Ý tưởng quan trọng là các vị trí cục bộ và giá trị ngăn xếp thường được lưu trữ gần nhau để thực thi hiệu quả.
Về mặt khái niệm:```text
frame memory
fixed metadata
locals and cells
value stack
```Trình biên dịch tính toán cần bao nhiêu vị trí cục bộ và vị trí ngăn xếp.
## 28:30 Khu vực Localsplus
CPython sử dụng một vùng kết hợp cho dữ liệu cục bộ và dữ liệu dạng ngăn xếp trong các khung thực thi của nó.
Bố cục khái niệm:```text
localsplus
fast locals
cell variables
free variables
value stack
```Vì:```python
def f(a, b):
c = a + b
return c
```Bố cục có thể được hiểu là:```text
localsplus
[0] a
[1] b
[2] c
[3...] value stack area
```Đối với các bao đóng, các ô và các biến tự do cũng chiếm các vị trí mà đối tượng mã đã biết.
Cách bố trí nhỏ gọn này làm giảm chi phí phân bổ và cải thiện địa phương.
## 28.31 Chuyển đổi trạng thái khung
Một khung có thể di chuyển qua nhiều trạng thái.```text
created
↓
executing
↓
returned
```Trình tạo và coroutine thêm nhiều trạng thái hơn:```text
created
↓
suspended
↓
executing
↓
suspended
↓
completed
```Một đường dẫn ngoại lệ:```text
executing
↓
exception raised
↓
handler found
↓
executing handler
```Hoặc:```text
executing
↓
exception raised
↓
no handler
↓
unwound
```Trạng thái khung xác định những hoạt động nào là hợp pháp. Một máy phát điện đã hoàn thành không thể tiếp tục. Không thể nhập lại máy phát điện đang chạy.
## 28.32 Thực thi lại
Việc thực thi Python có thể được thực hiện lại.
Một khung có thể thực thi một lệnh gọi mã người dùng, tạo ra một khung khác trước khi lệnh đầu tiên kết thúc.
Ví dụ:```python
class X:
def __add__(self, other):
return 42
def f(a, b):
return a + b
f(X(), X())
```các`BINARY_OP`hướng dẫn trong`f`cuộc gọi`X.__add__`, thực thi một khung Python khác.
Về mặt khái niệm:```text
frame f
BINARY_OP
calls __add__
frame X.__add__
return 42
continue frame f
```Đây là lý do tại sao mô hình đánh giá của CPython phải duy trì trạng thái khung trong các lệnh gọi của trình trợ giúp C có thể nhập lại Python.
## 28.33 Khung và ngăn xếp C
Khung Python và ngăn xếp C có liên quan nhưng khác biệt.
Lệnh gọi hàm Python tạo trạng thái thực thi Python. Tùy thuộc vào phiên bản CPython và đường dẫn cuộc gọi, ngăn xếp C cũng có thể phát triển.
Trình thông dịch đã làm việc theo thời gian để giảm sự đệ quy C không cần thiết trong các lệnh gọi Python, nhưng các lệnh gọi gốc, lệnh gọi tiện ích mở rộng và một số đường dẫn thời gian chạy vẫn liên quan đến ngăn xếp C.
Sự khác biệt quan trọng:```text
Python frame
Python-level execution state
C stack frame
native machine call state
```Truy nguyên Python hiển thị các khung Python, không phải mọi khung ngăn xếp C bên trong trình thông dịch.
## 28.34 Xóa khung
Các khung có thể được xóa để giải phóng các tham chiếu.
Một khung tham chiếu nhiều đối tượng:```text
locals
globals
builtins
stack values
trace function
exception state
previous frame
```Khi khung không còn cần thiết nữa, CPython phải giải phóng các tham chiếu đã sở hữu để có thể thu thập các đối tượng.
Đối với máy phát điện và coroutines, việc xóa phức tạp hơn vì khung có thể bị treo. Đóng trình tạo phải giải phóng trạng thái khung của nó một cách an toàn.
Ví dụ:```python
def gen():
data = bytearray(100_000_000)
yield 1
g = gen()
next(g)
del g
```Việc xóa trình tạo có thể giải phóng khung treo và do đó giải phóng`data`, giả sử không có tài liệu tham khảo nào khác tồn tại.
## 28.35 Ví dụ kiểm tra khung
Chương trình này in một ngăn xếp cuộc gọi đơn giản:```python
import sys
def print_stack():
frame = sys._getframe()
while frame is not None:
print(frame.f_code.co_name, frame.f_lineno)
frame = frame.f_back
def c():
print_stack()
def b():
c()
def a():
b()
a()
```Về mặt khái niệm, chuỗi là:```text
print_stack
c
b
a
<module>
```Số dòng chính xác phụ thuộc vào tập tin.
Ví dụ này cho thấy cách các khung hình thành một ngăn xếp thời gian chạy được liên kết hiển thị với mã Python.
## 28.36 Sự cân bằng trong thiết kế khung
Các khung nằm giữa hiệu suất và nội tâm.
CPython muốn các khung hình nhanh vì mọi lệnh gọi Python đều sử dụng chúng. Nhưng Python cũng hiển thị các khung cho mã người dùng và các công cụ.
Điều này tạo ra căng thẳng:
| Mục tiêu | Áp lực |
|---|---|
| Cuộc gọi nhanh | Giữ khung nhỏ gọn và bên trong |
| Gỡ lỗi | Hiển thị các đối tượng khung hình phong phú |
| Hồ sơ | Bảo toàn siêu dữ liệu cuộc gọi và đường dây |
| Truy nguyên | Giữ đủ trạng thái sau khi thất bại |
| Máy phát điện | Hỗ trợ đình chỉ |
| Coroutine | Hỗ trợ hệ thống treo không đồng bộ |
| Hiệu quả bộ nhớ | Tránh giữ lại những tài liệu tham khảo không cần thiết |
| Khả năng tương thích | Bảo tồn API khung cấp Python |
Phần lớn công việc của khung CPython là cân bằng các ràng buộc này.
## 28.37 Những hiểu lầm phổ biến
| Hiểu lầm | Đúng mẫu |
|---|---|
| Một khung giống như một đối tượng mã | Một khung thực thi một đối tượng mã |
| Mỗi đối tượng hàm có một khung | Mỗi cuộc gọi đang hoạt động đều có trạng thái khung riêng biệt |
| Các biến cục bộ luôn tồn tại trong một lệnh | Người dân địa phương chức năng thường sử dụng slot nhanh |
|`locals()`luôn là nơi lưu trữ thực sự | Trong các hàm, nó có thể là dạng xem hoặc ánh xạ được đồng bộ hóa |
| Dấu vết chỉ là chuỗi | Tracebacks khung tham chiếu và vị trí mã |
| Máy phát điện khởi động lại mỗi lần | Máy phát điện nối lại khung treo |
| Việc đóng cửa giữ cho toàn bộ khung bên ngoài tồn tại | Thông thường chúng giữ cho các tế bào cần thiết tồn tại |
| Khung nội tâm là miễn phí | Nó có thể ảnh hưởng đến hiệu suất và tuổi thọ bộ nhớ |
## 28.38 Chiến lược đọc thực hành
Để nghiên cứu các khung trong CPython, hãy bắt đầu từ hành vi ở cấp độ Python.
Sử dụng:```python
import dis
import inspect
import sys
```Nghiên cứu chức năng này:```python
def outer(x):
y = x + 1
def inner(z):
return x + y + z
return inner
```Thanh tra:```python
fn = outer(10)
print(outer.__code__.co_varnames)
print(outer.__code__.co_cellvars)
print(fn.__code__.co_freevars)
print(fn.__closure__)
dis.dis(outer)
dis.dis(fn)
```Sau đó ánh xạ đầu ra tới các khái niệm sau:```text
fast locals
cell variables
free variables
code objects
function objects
frames
stack effects
closure cells
```Điều này đưa ra một lộ trình cụ thể từ cú pháp Python đến phần bên trong khung.
## 28.39 Tóm tắt chương
Khung là bản ghi thực thi của CPython cho khối mã Python đang chạy. Nó liên kết một đối tượng mã với trạng thái thời gian chạy trực tiếp: cục bộ, toàn cục, nội dung, giá trị ngăn xếp, vị trí lệnh, trạng thái ngoại lệ và ngữ cảnh của người gọi.
Các khung giải thích các lệnh gọi hàm, thực thi mô-đun, nội dung lớp, truy nguyên, gỡ lỗi, lập hồ sơ, đệ quy, trình tạo, coroutine, bao đóng và lưu giữ bộ nhớ.
Mô hình chính là:```text
code object
immutable instructions and metadata
frame
mutable execution state for one execution
evaluation loop
runs the frame until return, exception, yield, or suspension
```Hiểu các khung làm cho vòng đánh giá trở nên cụ thể. Trình thông dịch không thực thi mã nguồn trừu tượng. Nó đang nâng cao trạng thái khung thông qua các hướng dẫn mã byte.