6.从源代码到执行

CPython 不直接执行Python 源文本。它在第一个字节码指令运行之前通过几种内部表示形式转换源文本。

路径是:```text source text ↓ tokens ↓ parse tree ↓ abstract syntax tree ↓ symbol table ↓ code object ↓ frame ↓ bytecode evaluation ↓ object operations


## 6.1 源文本

输入以文本开始。```python
x = 1 + 2
print(x)
``` CPython 执行此操作之前,它必须知道:```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
```Python源代码不仅仅是一个字符串。它有编码、行结构、缩进、注释、字符串文字规则和语法规则。

第一阶段将原始文本转换为标记。

## 6.2 代币化

标记生成器读取源字符并生成标记。

对于这段代码:```python
x = 1 + 2
```分词器生成类似于以下内容的流:```text
NAME("x")
EQUAL("=")
NUMBER("1")
PLUS("+")
NUMBER("2")
NEWLINE
ENDMARKER
```对于块结构,缩进也成为标记。```python
if ok:
    run()
else:
    stop()
```从概念上讲:```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
```这很重要。 Python 块结构稍后不会从空格推断出来。分词器发出显式的`INDENT``DEDENT`代币。

## 6.3 解析

解析器使用标记并检查它们是否形成有效的 Python 语法。

为了:```python
x = 1 + 2
```解析器识别右侧是二进制表达式的赋值语句。

为了:```python
def add(a, b):
    return a + b
```解析器识别:```text
function definition
function name
parameter list
function body
return statement
binary expression
```解析器拒绝无效的标记序列:```python
x = + * 3
```这到达了解析器,但无法简化为有效的语法。

解析器的输出是程序的结构化表示。然后,CPython 将该结构转换为 AST

## 6.4 抽象语法树

AST 表示程序的语义结构。

为了:```python
x = 1 + 2
```AST 在概念上是:```text
Module
    Assign
        target: Name("x", Store)
        value:
            BinOp
                left: Constant(1)
                op: Add
                right: Constant(2)
```AST 删除了许多表面细节并保留了后续编译阶段所需的结构。

您可以从 Python 检查 AST```python
import ast

tree = ast.parse("x = 1 + 2")
print(ast.dump(tree, indent=4))
```输出形状示例:```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 说明了程序的结构含义。它还没有说明要发出哪些字节码指令。

## 6.5 名称上下文

AST 名称带有上下文。

在此代码中:```python
x = x + 1
```的两种用途`x`有不同的角色。```text
left side x     Store
right side x    Load
```从概念上讲:```text
Assign
    target: Name("x", Store)
    value:
        BinOp
            left: Name("x", Load)
            op: Add
            right: Constant(1)
```这种区别很重要,因为加载名称和存储名称会编译为不同的操作。```text
Load context     read a value
Store context    assign a value
Del context      delete a binding
```编译器在发出字节码时依赖此信息。

## 6.6 符号表分析

在生成字节码之前,CPython 会分析名称。

它决定每个名称是否:```text
local
global
nonlocal
free
cell
implicit builtin lookup
```例子:```python
x = 10

def f(y):
    return x + y
```里面`f`:

```text
y is local
x is global or builtin lookup
```另一个例子:```python
def outer():
    x = 10

    def inner():
        return x

    return inner
```这里:```text
x is local in outer
x is free in inner
x becomes a cell variable in outer
```单元变量是一个局部变量,它必须存在,因为内部函数捕获了它。自由变量是由函数使用但存储在封闭范围内的变量。

此阶段对于关闭至关重要。

## 6.7 代码对象

经过语法分析和符号分析后,CPython 将代码编译为代码对象。

代码对象包含:```text
bytecode
constants
names
local variable names
free variable names
cell variable names
stack size
flags
filename
function name
line mapping information
exception table
```您可以检查代码对象:```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)
```代码对象是不可变的。它描述可执行代码,但不包含局部变量的当前运行时值。

## 6.8 字节码

字节码是CPython的指令格式。

为了:```python
def add(a, b):
    return a + b
```反汇编在概念上可能类似于:```text
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```实际字节码名称和布局因 Python 版本而异。核心思想仍然是:字节码指令在帧上运行。

使用`dis`:

```python
import dis

def add(a, b):
    return a + b

dis.dis(add)
```字节码的级别比 AST 低。已经接近执行了。

AST 说:```text
return a + b
```字节码说:```text
load a
load b
perform addition
return result
```## 6.9 常量和名称

代码对象将常量和名称与字节码分开存储。

为了:```python
x = 10
print(x)
```常数`10`存储在常量表中。名字`x``print`存储在名称表中。

从概念上讲:```text
co_consts = (10, None)
co_names  = ("x", "print")
```然后字节码通过索引引用这些表。```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
```这使得字节码变得紧凑。指令存储小索引而不是完整的字符串或对象。

## 6.10 模块执行

Python 文件被编译成模块级代码对象。

为了:```python
# demo.py
x = 1

def f():
    return x
```CPython 将整个文件编译成一个代码对象。执行该代码对象会创建模块绑定。

从概念上讲:```text
create module object
create module dictionary
execute module code object in that dictionary
bind x
create function object f
bind f
```函数体也被编译成它自己的代码对象。模块代码对象包含该函数代码对象作为常量。

这解释了为什么定义函数会在模块导入时执行代码:主体不会运行,但会创建并绑定函数对象。

## 6.11 函数定义

函数定义是可执行代码。

为了:```python
def add(a, b):
    return a + b
```CPython 不会立即运行主体。它创建一个函数对象。

从概念上讲:```text
load code object for add
load function name
create function object
store function object in current namespace
```函数对象包含:```text
code object
globals dictionary
default values
closure cells
annotations
metadata
```稍后,当调用该函数时,CPython 从该函数对象创建一个框架并执行该函数的代码对象。

## 6.12 框架创建

 CPython 执行代码对象时会创建一个框架。

对于函数调用:```python
add(2, 3)
```CPython 使用以下内容创建一个框架:```text
code object for add
globals from add.__globals__
builtins
local slots
argument values
value stack
instruction pointer
exception state
```参数被放置到局部变量槽中。```text
a = 2
b = 3
```然后字节码评估器开始执行该帧。

## 6.13 评估堆栈

大多数字节码指令通过帧的值堆栈进行通信。

为了:```python
return a + b
```执行是:```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
```局部变量与临时堆栈值分开存储。```text
locals:
    a = 2
    b = 3

stack:
    temporary values used by bytecode
```这就是为什么 CPython 被称为基于堆栈的虚拟机。

## 6.14 对象操作

字节码指令对 Python 对象进行操作,而不是对原始 C 原语进行操作。

CPython执行时:```python
a + b
```它并不假设`a``b`是机器整数。

他们可能是:```text
integers
floats
strings
lists
tuples
NumPy arrays
user-defined objects
```该操作通过对象协议进行调度。

为了`int + int`CPython使用整数加法。为了`str + str`,它使用字符串连接。对于用户定义的类,它可能会调用`__add__`

从概念上讲:```text
BINARY_OP +
    inspect operand types
    find numeric operation
    call appropriate slot
    return Python object
```这就是为什么字节码保持通用而类型提供具体行为的原因。

## 6.15 属性访问

对于:```python
obj.name
```CPython 编译属性加载。

从概念上讲:```text
LOAD_FAST obj
LOAD_ATTR name
```在运行时,`LOAD_ATTR`执行Python属性查找规则:```text
check type descriptors
check instance dictionary
check non-data descriptors and class attributes
possibly call __getattr__
raise AttributeError if missing
```一般情况下,属性访问不是原始字段查找。这是一个协议操作。

这就解释了为什么属性访问可以运行Python代码。```python
class C:
    @property
    def name(self):
        print("computed")
        return 42

obj = C()
obj.name
```属性读取调用描述符代码。

## 6.16 调用

对于:```python
result = f(2, 3)
```CPython 计算可调用对象和参数,然后执行调用。

从概念上讲:```text
load f
load 2
load 3
call with 2 positional arguments
store result
```在运行时,可调用对象可能是:```text
Python function
built-in C function
bound method
class object
object with __call__
partial object
method descriptor
```Python 函数调用创建一个新框架。 C 内置调用调用 C 函数包装器。类调用分配并初始化一个实例。

字节码指令是通用的。运行时调度决定确切的调用路径。

## 6.17 控制流程

控制流被编译成跳转。

为了:```python
if x:
    a()
else:
    b()
```CPython 发出的字节码形状如下:```text
load x
jump if false to else
call a
jump to end
else:
call b
end:
```For 循环编译成迭代器协议操作加上跳转。```python
for x in items:
    use(x)
```从概念上讲:```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:
```语言特征高。执行模型是字节码跳转和协议调用。

## 6.18 异常处理

异常处理编译为受保护的字节码范围和处理程序元数据。

为了:```python
try:
    risky()
except ValueError:
    recover()
```CPython 需要知道:```text
which bytecode range is protected
where the handler starts
which exception type to match
how to unwind the stack
where execution continues
```当异常发生时,评估器会查阅异常处理元数据,如果匹配,则将控制权转移到适当的处理程序。

如果当前帧中没有匹配的处理程序,则异常将传播到调用者。

## 6.19 进口

import 语句是可执行代码。```python
import math
```在运行时,CPython 使用导入机制来:```text
check sys.modules
find a module spec
load or create the module
execute module code if needed
bind the name
```导入系统部分通过Python实现`importlib`并且部分由 C 运行时代码支持。

模块文件的编译和执行就像其他Python代码一样,但它的执行命名空间是模块字典。

## 6.20 推导式

推导式有其自己的执行范围。

为了:```python
squares = [x * x for x in range(10)]
```CPython 为理解体创建代码。

从概念上讲:```text
call range(10)
get iterator
create result list
run comprehension code
append each computed value
store final list in squares
```循环变量`x`属于推导式的内部作用域,而不是周围的函数作用域。

这就是为什么:```python
[x for x in range(3)]
print(x)
```不绑定`x`在现代 Python 的周围范围内。

## 6.21 关闭

闭包需要单元格。

为了:```python
def outer():
    x = 10

    def inner():
        return x

    return inner

inner使用来自的变量outer

CPython无法存储x作为一个普通的快速本地,当outer返回。它存储x在单元格对象中。

从概念上讲: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 这就是返回的函数仍然有效的原因:```python f = outer() print(f())


## 6.22 生成器

生成器函数的编译方式与普通函数不同。```python
def count():
    yield 1
    yield 2
```呼唤`count()`创建一个生成器对象。它不会立即运行身体。

生成器对象存储挂起的执行状态:```text
code object
frame or frame-like execution state
instruction offset
local variables
value stack state
running status
```每个`next()`恢复执行直到下一个`yield````python
g = count()
next(g)
next(g)
```一个`yield`不仅仅是回报。它挂起帧并保留执行状态。

## 6.23 协程

协程类似于生成器,但它参与`await`协议。```python
async def fetch():
    value = await operation()
    return value
```呼唤`fetch()`创建一个协程对象。主体仅在等待或安排协程时运行。

协程存储挂起的执行状态并在附近恢复`await`点。

从概念上讲:```text
create coroutine object
start execution
reach await
suspend coroutine
resume later with result
continue execution
return final value
```事件循环位于核心字节码模型之外,但协程挂起和恢复是由 CPython 对象和框架实现的运行时功能。

## 6.24 类定义

类语句是可执行代码。```python
class C:
    x = 1

    def f(self):
        return self.x
```CPython 并不简单地分配静态类型。它在临时命名空间中执行类主体。

从概念上讲:```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
```这解释了为什么类体可以包含任意代码:```python
class C:
    print("building class")
    x = 1 + 2
```当类语句运行时,类主体立即执行。

## 6.25 端到端示例

考虑:```python
x = 10

def add(y):
    return x + y

print(add(5))
```管道是:```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
```重要的一点是,在第一行出现运行之前,CPython 已经完成了大量工作。

## 6.26 每个阶段所在的位置

一个有用的源地图:```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
```确切的文件名会随着时间的推移而变化,但该映射对于读取存储库来说足够稳定。

## 6.27 心智模型

保持这个紧凑的模型:```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.
```整个系统很大,但这个序列是主干。

## 6.28 章节总结

CPython 执行是一个管道。它以源文本开始,以字节码求值器内的对象操作结束。分词器处理字符。解析器处理语法。 AST 代表结构。符号表对名称进行分类。编译器生成代码对象。框架执行代码对象。字节码通过类型定义的行为来操作Python对象。

该管道解释了高级 Python 构造如何成为具体的运行时操作。