5. 运行时模型

CPython 运行时是进程启动之后、Python 代码执行完成之前存在的机制。它拥有解释器状态、线程状态、模块、内置函数、内存分配器、异常状态、导入状态、帧、挂起调用、信号处理和关闭行为。

Python 程序看起来是作为一系列语句运行的。 CPython 在分层运行时系统中运行它。```text operating system process CPython runtime interpreter state thread state frame stack executing code object bytecode instructions object operations


## 5.1 进程、运行时、解释器、线程、框架

正在运行的 CPython 程序有多个嵌套的执行单元。

|单位|意义|
| ------------ | -------------------------------------------------------------------------------- |
|流程|包含 CPython 可执行文件或嵌入式运行时的操作系统进程 |
|运行时 |跨进程共享全局 CPython 状态 |
|口译 |运行时内的隔离 Python 解释器状态 |
|线程状态 |一个解释器的每线程执行状态 |
|框架|一个活动的执行上下文 |
|代码对象|编译的字节码和元数据 |
|对象|由字节码操作的运行时值 |

一个简单的脚本:```python
x = 1
print(x + 2)
```在框架内运行。该帧属于线程状态。线程状态属于解释器。解释器属于 CPython 运行时。

从概念上讲:```text
_PyRuntimeState
    PyInterpreterState
        PyThreadState
            PyFrameObject or internal frame
                PyCodeObject
                locals
                globals
                builtins
                value stack
```确切的 C 结构因版本而异,但层次结构足够稳定,可以指导源代码阅读。

## 5.2 运行时

运行时是进程范围的 CPython 状态。

它包括一名或多名口译员使用的全球服务:```text
runtime initialization state
memory allocator state
interpreter list
GIL state
pending calls
signal handling state
audit hook state
global caches
runtime finalization flags
```在较高的层面上,运行时的答案是:```text
Has CPython been initialized?
Which interpreters exist?
Is the runtime finalizing?
Which global locks and services are active?
Which process-wide hooks are installed?
```这在启动、关闭、嵌入、子解释器和自由线程工作期间最重要。

一个普通的 Python 脚本通常有一个运行时和一个主解释器。

## 5.3 解释器状态

解释器状态代表进程内的一个 Python 解释器。

它拥有语言级状态,例如:```text
modules dictionary
builtins
import state
sys module state
codec state
warnings state
garbage collector state
interned strings
per-interpreter caches
execution configuration
```从概念上讲:```text
PyInterpreterState
    modules
    builtins
    sysdict
    import machinery
    gc generations
    codec registry
    pending async exception state
```大多数 Python 程序仅使用主解释器。

子解释器在同一进程内创建额外的解释器状态。它们可以具有单独的模块字典和运行时状态,但它们仍然共享一些进程级资源。

## 5.4 线程状态

每个执行Python代码的操作系统线程都需要一个线程状态。

线程状态存储一个解释器中一个线程的执行信息:```text
current frame
current exception state
recursion depth
profiling function
tracing function
async exception request
thread-local interpreter data
```线程状态将本机执行连接到 Python 执行。

从概念上讲:```text
current OS thread
    PyThreadState
        current interpreter
        current frame
        exception information
        tracing and profiling hooks
``` C 代码需要引发异常、访问当前帧或与 Python API 交互时,通常需要当前线程状态。

## 5.5 帧

帧是一个执行记录。

CPython 在执行模块体、函数体、类体、生成器、协程或推导式时创建一个框架。

一个框架包含:```text
code object
globals dictionary
builtins dictionary
locals storage
value stack
instruction pointer
block and exception state
line number state
owner information
```对于一个函数:```python
def add(a, b):
    c = a + b
    return c
```呼叫`add(2, 3)`为该调用创建一个框架。

框架存储:```text
code object for add
a = 2
b = 3
c after assignment
temporary stack values
current bytecode offset
globals of the defining module
builtins visible to the function
```框架是使函数调用处于活动状态的具体运行时对象。

## 5.6 代码对象

代码对象是编译后的可执行内容。

它是不可变的元数据加上字节码。它不存储局部变量的当前值。

例如:```python
def f(x):
    y = x + 1
    return y
```函数对象有一个代码对象:```python
code = f.__code__

print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
```代码对象说明如何执行该函数。该帧存储该代码的一次活动执行。

这种区别很重要:```text
code object: reusable compiled program
frame: one running invocation of that program
function: callable object that wraps code with globals, defaults, and closure
```对同一函数的多次调用重用同一代码对象,但会创建单独的框架。

## 5.7 函数对象

Python 函数对象用运行时上下文包装代码对象。

它包含:```text
code object
globals dictionary
defaults
keyword-only defaults
closure cells
annotations
function name
qualified name
module name
dict for custom attributes
```例如:```python
x = 10

def f(y):
    return x + y
```功能`f`需要的不仅仅是字节码。它还需要全局字典,其中`x`可以找到。

从概念上讲:```text
PyFunctionObject
    func_code      code for f
    func_globals   module globals
    func_defaults  default argument values
    func_closure   captured cells
```调用该函数会使用这些组件创建一个框架。

## 5.8 对象和类型

在运行时,字节码操作对象引用。

每个 Python 值都是一个对象:```python
42
"hello"
[1, 2, 3]
{"a": 1}
lambda x: x + 1
Exception("bad")
```每个对象都有一个类型。

类型决定行为:```text
allocation
deallocation
attribute lookup
method lookup
call behavior
numeric operations
sequence operations
mapping operations
iteration
representation
hashing
comparison
```例如:```python
len(x)
```不直接检查每个可能的对象布局。它询问对象的类型长度如何工作。

这就是运行时模型依赖于类型对象的原因。字节码指令是通用的。类型槽提供具体的行为。

## 5.9 命名空间

Python 执行使用命名空间。

主要的命名空间类别有:

|命名空间|后备存储 |
| ----------------- | ------------------------------------------------- |
|当地人 |函数快速局部变量、类命名空间或映射 |
|全局变量 |模块词典|
|内置函数 |内置字典 |
|对象属性 |实例字典、槽、描述符、类型查找 |
|模块属性 |模块词典|

对于这段代码:```python
x = 1

def f():
    return x + len([1, 2])
```里面`f`CPython解析:```text
x     global name in module dictionary
len   builtin name if not found in globals
[1,2] newly created list object
```名称解析取决于编​​译时分类和运行时命名空间查找。

## 5.10 快速本地化

函数局部变量通常存储在优化的类似数组的布局中,而不是每次访问时都进行正常的字典查找。

对于一个函数:```python
def f(a, b):
    c = a + b
    return c
```编译器知道本地名称:```text
a
b
c
```框架可以将它们存储在索引槽中。

然后字节码可以使用快速本地操作:```text
LOAD_FAST a
LOAD_FAST b
STORE_FAST c
```这比字典查找更快。

本地字典可以在需要时具体化,例如通过`locals()`、跟踪、调试或帧检查。但正常的执行路径使用快速局部变量。

## 5.11 价值堆栈

CPython 字节码使用堆栈机。

指令从帧的值堆栈中推送和弹出临时值。

为了:```python
z = x + y
```概念执行是:```text
LOAD_FAST x       push x
LOAD_FAST y       push y
BINARY_OP +       pop x and y, push result
STORE_FAST z      pop result into local z
```值栈是临时执行存储。它与局部变量分开。

因此,一个框架包含:```text
local variable storage
value stack for intermediate operations
```这就解释了为什么字节码可以很紧凑。指令通过堆栈进行通信。

## 5.12 调用

函数调用是最重要的运行时操作之一。

为了:```python
result = f(1, 2)
```CPython 必须:```text
evaluate f
evaluate arguments
prepare call layout
check callable type
create or enter callable execution
bind arguments
execute body or C function
return result
```不同的可调用对象有不同的路径:

|可调用 |运行时路径 |
| ---------------------- | ------------------------------------------------------ |
| Python 函数 |创建框架并执行代码对象 |
|内置功能|调用 C 函数包装器 |
|方法|绑定接收者并调用底层函数 |
|班级 |分配并初始化实例 |
|对象与`__call__`| Invoke类型调用槽 |

CPython 优化了调用约定以减少临时元组和字典分配。现代 CPython 对许多可调用类型使用快速调用路径,例如向量调用。

## 5.13 例外情况

例外是运行时状态加上控制流。

 Python 代码出现时:```python
raise ValueError("bad")
```CPython记录当前线程状态中的异常信息并开始展开执行。

C 函数通常通过以下方式报告失败:```text
setting an exception
returning NULL or -1
```调用者检查返回值并传播失败。

从概念上讲:```c
PyErr_SetString(PyExc_ValueError, "bad");
return NULL;
```在字节码级别,异常会影响:```text
current frame
exception table
stack unwinding
finally blocks
except matching
traceback construction
propagation to caller
```异常不是普通的返回值。它们是运行时的单独控制路径。

## 5.14 回溯

回溯记录异常发生的位置。

当异常通过帧传播时,CPython 可以附加回溯条目来标识:```text
file name
function name
line number
bytecode position
frame
```例如:```python
def a():
    b()

def b():
    1 / 0

a()
```回溯包含调用链:```text
module frame
a frame
b frame
```回溯是对象。保留回溯可以使帧保持活动状态。保持帧可以使局部变量保持活动状态。这对于记忆行为很重要。

## 5.15 模块

模块是具有命名空间的对象。

 CPython 导入模块时,它会创建或检索模块对象并执行该模块字典中的代码。

从概念上讲:```text
find module spec
create module object
insert into sys.modules
execute module code in module namespace
return module object
```模块字典成为该模块中定义的函数的全局命名空间。

为了:```python
# example.py
x = 10

def f():
    return x
```函数`f`存储对模块全局字典的引用。什么时候`f`抬头看`x`,它会搜索该字典。

## 5.16`sys.modules`

`sys.modules`是导入缓存。

它将模块名称映射到模块对象。```python
import sys
print(sys.modules["sys"])
```此缓存可防止重复导入重新执行同一模块。

导入同一个模块两次通常会返回已经加载的模块:```python
import math
import math
```第二次进口检查`sys.modules`并重用该模块。

该缓存还处理循环导入。模块可能出现在`sys.modules`在其代码执行完毕之前。

## 5.17 内置函数

内置函数是本地和全局查找失败时可用的名称。

示例:```python
len
print
range
object
type
Exception
```框架可以访问内置字典。

函数内非限定名称的名称查找大致如下:```text
locals
globals
builtins
```所以:```python
def f(xs):
    return len(xs)
```通常会解决`len`来自内置函数,除非全局命名`len`遮蔽它。

该查找路径是运行时模型的一部分。它解释了为什么分配一个全局命名`len`改变模块内部的行为。

## 5.18 描述符和属性访问

属性访问是运行时调度。

为了:```python
obj.name
```CPython 不只是简单地看内部`obj.__dict__`

它遵循描述符和类型查找规则:```text
look on type for data descriptor
look in instance dictionary
look on type for non-data descriptor or class attribute
call __getattr__ if needed
raise AttributeError if missing
```这就是方法自动绑定的原因:```python
class C:
    def f(self):
        return 1

c = C()
m = c.f
```存储在类上的函数对象是一个描述符。通过实例访问它会创建绑定方法或优化的等效调用路径。

属性查找连接对象布局、类型对象、描述符、方法调用和性能。

## 5.19 迭代

迭代使用小型运行时协议。

为了:```python
for x in obj:
    body(x)
```CPython 大致做了:```text
iterator = iter(obj)
loop:
    x = next(iterator)
    if StopIteration: exit loop
    execute body
``` C 级别,这映射到类型槽和协议助手。

迭代器对象存储迭代状态。对于列表迭代器,该状态包括列表和当前索引。对于生成器来说,迭代器就是挂起的执行帧本身。

该通用协议具有以下功能:```text
for loops
comprehensions
tuple unpacking
list()
sum()
any()
all()
many standard library functions
```## 5.20 生成器和协程

发电机是一个悬挂的框架。

为了:```python
def gen():
    yield 1
    yield 2
```呼叫`gen()`不立即执行主体。它创建一个生成器对象。

生成器拥有执行状态:```text
code object
suspended frame or frame-like state
instruction position
local variables
exception state
running flag
```呼唤`next()`恢复执行直到下一个`yield`或返回。

协程和异步生成器通过可等待的协议行为和事件循环集成扩展了这个想法。

## 5.21 内存管理

运行时在多个层管理内存。```text
raw memory allocator
object allocator
type-specific free lists or caches
reference counting
cyclic garbage collector
```引用计数处理大多数生命周期事件:```text
new reference increases lifetime
decref releases ownership
zero refcount triggers deallocation
```循环垃圾收集器处理无法访问的对象循环。

内存管理是运行时行为的一部分,因为对象销毁可以通过终结器、weakref 回调或释放更多对象的释放路径来执行代码。

## 5.22 运行时模型中的 GIL

在传统的 CPython 运行时中,全局解释器锁保护 Python 字节码和许多内部数据结构的执行。

GIL 简化了:```text
reference count updates
object mutation invariants
interpreter state access
C extension assumptions
```线程必须持有 GIL 才能执行 Python 字节码。

C 扩展可以围绕阻塞或长时间运行的本机工作释放 GIL,然后在再次接触 Python 对象之前重新获取它。

因此,运行时模型有两层并发:```text
OS threads may run concurrently
Python bytecode execution is serialized by the GIL in traditional builds
```现代 CPython 还具有自由线程构建工作,这改变了许多内部假设。但传统的 GIL 模型对于理解现有代码和扩展仍然至关重要。

## 5.23 初始化

 Python 代码运行之前,CPython 会初始化运行时。

启动包括:```text
configure memory allocators
initialize runtime state
create main interpreter
create main thread state
initialize builtins
initialize sys
set up import machinery
initialize encodings
configure paths
process command-line options
run startup hooks
execute requested code
```这就是启动代码复杂的原因。许多模块依赖于已经存在的其他模块,但导入系统本身也需要运行时支持。

CPython 会小心地引导自身。

## 5.24 关机

关机也很复杂。

最终确定可能涉及:```text
running atexit handlers
flushing standard streams
clearing modules
destroying interpreter state
collecting garbage
finalizing objects
releasing memory
tearing down runtime services
```对象可能在关闭期间运行终结器,但它们的模块全局变量可能已被清除。这就是为什么关闭错误可能很微妙。

期望的终结器`sys`, `os`,或者另一个完全可用的模块可能会在解释器拆卸期间失败。

## 5.25 嵌入式Python

CPython 可以嵌入到另一个 C  C++ 程序中。

在这种情况下,主机进程控制运行时初始化和终止。

从概念上讲:```c
Py_Initialize();
/* run Python code */
Py_Finalize();
```嵌入使运行时层次结构更加明显。 CPython 运行时位于一个进程内,该进程可能有自己的线程、分配器、事件循环、日志系统和关闭规则。

嵌入代码必须遵守:```text
initialization order
thread state management
GIL rules
reference ownership
exception handling
finalization constraints
```## 5.26 运行时状态和可观察性

Python 通过标准模块公开部分运行时。

|模块|运行时视图 |
| ------------- | ------------------------------------------------------ |
|`sys`|解释器设置、模块、路径、框架、引用计数 |
|`gc`|垃圾收集状态 |
|`inspect`|框架、函数、来源、签名 |
|`dis`|字节码 |
|`types`|运行时类型对象 |
|`threading`| Python 线程抽象 |
|`tracemalloc`|分配痕迹|
|`importlib`|进口机械|

示例:```python
import sys
import gc
import inspect

print(sys.modules.keys())
print(gc.get_count())
print(inspect.currentframe())
```这些模块很有用,因为它们可以让您观察运行时结构,而无需立即阅读 C 代码。

## 5.27 完整的执行草图

对于这个程序:```python
def add(a, b):
    return a + b

print(add(2, 3))
```CPython 大致是这样的:```text
initialize runtime
create main interpreter
create main thread state
load builtins and sys
compile module source to code object
create module frame
execute module bytecode

define function:
    create code object for add
    create function object
    bind name add in module globals

call print(add(2, 3)):
    load print from builtins
    load add from globals
    load constants 2 and 3
    call add
        create frame for add
        bind a = 2, b = 3
        load a
        load b
        perform binary addition through object protocol
        return integer result
    call print
        execute built-in C function
        write output
    discard return value

finish module frame
run shutdown sequence
```这是正在运行的运行时模型。

## 5.28 工作心智模型

在阅读后面的章节时保持这个紧凑的模型:```text
A process owns a CPython runtime.
The runtime owns interpreters.
An interpreter owns modules, builtins, import state, and GC state.
A thread state owns the current execution state for one thread.
A frame runs one code object.
Bytecode instructions operate on a value stack and local storage.
Objects carry type pointers.
Types define behavior through slots.
Errors use exception state plus sentinel returns.
Memory is managed by reference counting plus cyclic GC.
```该模型连接了大多数 CPython 内部结构。

## 5.29 章节总结

CPython 运行时是一个分层执行系统。该进程包含一个运行时。运行时包含解释器状态。每个正在执行的线程都有一个线程状态。每个活动呼叫都有一个框架。每个框架运行一个代码对象。字节码操作对象引用。对象指向类型。类型定义行为。

理解这个层次结构可以让 CPython 的其余部分更容易阅读。启动、导入、函数调用、异常、生成器、垃圾收集和关闭都适合同一运行时模型。