23. 代码对象

代码对象是 CPython 的可执行 Python 代码的编译表示。

它包含字节码和元数据。解释器可以执行它,但代码对象本身不携带运行时状态,例如全局变量、默认参数、闭包单元或绑定方法。

对于这个来源:python def add(a, b): return a + b 函数对象add包含一个代码对象:```python code = add.code

print(code.co_name) print(code.co_varnames) print(code.co_consts) print(code.co_names)


## 23.1 在编译管道中的位置

代码对象是编译的输出。```text
source text
    
tokenization
    
parsing
    
AST
    
symbol table
    
compiler
    
code object
    
frame execution
```代码对象是编译器和解释器之间的切换。

编译器生成代码对象。

评估循环执行帧内的代码对象。

## 23.2 代码对象与函数对象

函数对象包装了代码对象。

例子:```python
def f(x):
    return x + 1
```运行时:```text
function object
    __code__       code object
    __globals__    module globals dictionary
    __defaults__   positional defaults
    __kwdefaults__ keyword-only defaults
    __closure__    closure cells
    __dict__       function attributes
    __name__       function name
    __qualname__   qualified name
```代码对象包含编译后的主体。```text
code object
    bytecode
    constants
    names
    local variable names
    free variable names
    cell variable names
    filename
    line information
    stack size
    flags
```理论上,同一个代码对象可以被多个函数对象使用。```python
import types

def f(x):
    return x + 1

g = types.FunctionType(f.__code__, globals(), "g")

print(g(10))
```代码已共享。函数包装器更改运行时绑定。

## 23.3 代码对象是不可变的

代码对象是不可变的。

一旦创建,代码对象的字节码、常量、变量名、标志和元数据就不能就地更改。

这对于安全性和运行时设计很重要。多个函数或框架可以引用相同的代码对象。如果代码对象是可变的,则更改对象可能会影响当前正在运行的代码。

为了修改代码,工具创建一个新的代码对象。

现代Python公开了一个`replace()`方法:```python
def f():
    return 1

new_code = f.__code__.replace(co_name="renamed")
```这将创建修改后的副本,而不是编辑原始对象。

## 23.4 模块代码对象

模块也有一个代码对象。

例子:```python
src = """
x = 1
y = 2
print(x + y)
"""

code = compile(src, "example.py", "exec")
```该代码对象代表顶层执行。

它没有函数参数。它的本地命名空间通常是模块字典。

检查:```python
print(code.co_name)
print(code.co_filename)
print(code.co_consts)
print(code.co_names)
print(code.co_varnames)
```对于模块代码,`co_name`经常是`"<module>"`

解释器使用全局变量和局部变量执行此代码对象。正常导入、脚本执行和`exec()`全部使用模块样式的代码对象。

## 23.5 表达式代码对象`compile()`还可以生成表达式代码对象。

例子:```python
code = compile("1 + 2", "<input>", "eval")
result = eval(code)

print(result)
```一个`eval`代码对象代表一个表达式。它返回表达式值。

这不同于`exec`模式:```python
compile("x = 1", "<input>", "exec")
compile("1 + 2", "<input>", "eval")

exec模式接受语句。evalmode 只接受一个表达式。

该模式会更改语法起点和生成的代码对象行为。

23.6 交互式代码对象

交互模式使用"single"汇编。

例子:```python code = compile("1 + 2", "", "single") exec(code)


这就是 REPL 的行为与脚本执行不同的原因。```text
exec mode:
    execute statements normally

eval mode:
    return expression value

single mode:
    behave like interactive input
```代码对象记录了解释器正确运行所选模式所需的足够信息。

## 23.7 嵌套代码对象

嵌套的可执行结构生成嵌套的代码对象。

例子:```python
def outer(x):
    def inner(y):
        return x + y
    return inner
```模块代码对象包含以下代码对象`outer``co_consts`

`outer`代码对象包含以下代码对象`inner`在它自己的`co_consts`

检查:```python
def outer(x):
    def inner(y):
        return x + y
    return inner

outer_code = outer.__code__
print(outer_code.co_consts)

for const in outer_code.co_consts:
    if isinstance(const, type(outer_code)):
        print("nested code:", const.co_name)
```嵌套代码对象是常量,因为它们是嵌入在封闭代码对象中的编译数据。

在运行时,`MAKE_FUNCTION`从这些代码对象创建函数对象。

## 23.8`co_code`

`co_code`存储字节码字节。

例子:```python
def f(a, b):
    return a + b

print(f.__code__.co_code)
```生的`co_code`直接阅读并不愉快。使用`dis`:

```python
import dis

dis.dis(f)
```字节码是 CPython 虚拟机的指令流。

每条指令都有一个操作码,可能还有一个参数。确切的编码因版本而异,因此工具应该使用`dis`而不是硬编码字节偏移量。

## 23.9`co_consts`

`co_consts`存储字节码引用的常量。

例子:```python
def f():
    return 1, "x", None
```检查:```python
print(f.__code__.co_consts)
```典型内容:```text
(None, 1, 'x')
```常量可能包括:```text
None
booleans
numbers
strings
bytes
tuples of constants
frozensets
nested code objects
```编译器会删除代码对象中的一些常量。

字节码指令按索引加载常量:```text
LOAD_CONST 1
```意思是“负载`co_consts[1]`”。

## 23.10`co_names`

`co_names`存储用于全局查找、属性查找、导入和类似操作的名称。

例子:```python
def f(xs):
    return len(xs)
```检查:```python
print(f.__code__.co_names)
```可能的输出:```text
('len',)
```字节码使用索引`co_names`:

```text
LOAD_GLOBAL 0
```意思是“加载全局或内置名称`co_names[0]`”。

属性访问示例:```python
def f(obj):
    return obj.value

value出现在co_names因为属性名称也存储在那里。

23.11co_varnames

co_varnames存储局部变量名称。

例子:python def f(a, b): c = a + b return c 检查:python print(f.__code__.co_varnames) 典型输出:text ('a', 'b', 'c') 快速本地字节码使用该元组的索引。```text LOAD_FAST 0 a LOAD_FAST 1 b STORE_FAST 2 c


## 23.12`co_freevars`和`co_cellvars`闭包使用两个代码对象字段。

|领域|意义|
| ------------- | ---------------------------------------------------- |
|`co_cellvars`|嵌套范围捕获的局部变量 |
|`co_freevars`|从封闭范围捕获的变量 |

例子:```python
def outer():
    x = 1

    def inner():
        return x

    return inner
```检查:```python
print(outer.__code__.co_cellvars)

inner = outer()
print(inner.__code__.co_freevars)
```预期形状:```text
('x',)
('x',)
```为了`outer`, `x`是一个单元格变量,因为嵌套代码需要它。

为了`inner`, `x`是一个自由变量,因为它来自封闭范围。

## 23.13 闭包单元不存储在代码对象中

代码对象记录它需要哪些自由变量。它不存储实际捕获的值。

捕获的值存在于附加到函数对象的闭包单元中。

例子:```python
def make_reader(value):
    def read():
        return value
    return read

f = make_reader(42)

print(f.__code__.co_freevars)
print(f.__closure__)
print(f.__closure__[0].cell_contents)
```代码对象说:```text
this function needs a free variable named value
```函数对象提供:```text
the actual cell containing 42
```这种分离使得相同的嵌套代码对象可以与不同的捕获值一起重用。

## 23.14`co_argcount`和参数元数据

代码对象存储参数计数。

重要字段包括:```text
co_argcount
co_posonlyargcount
co_kwonlyargcount
co_varnames
co_flags
```例子:```python
def f(a, b, /, c, *, d):
    return a, b, c, d
```检查:```python
code = f.__code__

print(code.co_argcount)
print(code.co_posonlyargcount)
print(code.co_kwonlyargcount)
print(code.co_varnames)
```这些字段帮助函数调用机制将参数绑定到局部变量槽。

默认值不存储在代码对象中。它们存在于函数对象上:```python
def f(x=1):
    return x

print(f.__defaults__)
print(f.__code__.co_consts)
```默认值属于函数对象,因为默认值是在函数定义时计算的。

## 23.15`co_flags`

`co_flags`存储执行标志。

标志描述如下属性:```text
has *args
has **kwargs
is generator
is coroutine
is async generator
uses nested scopes
future flags
```例子:```python
def normal():
    return 1

def gen():
    yield 1

async def coro():
    return 1
```检查:```python
print(normal.__code__.co_flags)
print(gen.__code__.co_flags)
print(coro.__code__.co_flags)
```运行时使用这些标志来决定调用函数时要创建什么对象。

生成器函数返回一个生成器对象。

协程函数返回一个协程对象。

普通函数正常执行并返回一个值。

## 23.16`co_stacksize`

`co_stacksize`记录代码对象所需的最大评估堆栈深度。

例子:```python
def f(a, b, c):
    return a + b * c
```编译器根据字节码堆栈效果计算堆栈使用情况。

从概念上讲:```text
LOAD_FAST a       stack depth 1
LOAD_FAST b       stack depth 2
LOAD_FAST c       stack depth 3
BINARY_OP *       stack depth 2
BINARY_OP +       stack depth 1
RETURN_VALUE      stack depth 0
```最大深度:3

该框架采用`co_stacksize`分配足够的堆栈存储空间。

## 23.17 源元数据

代码对象存储源元数据。

重要字段包括:```text
co_filename
co_name
co_qualname
co_firstlineno
line table data
position table data
```例子:```python
def f():
    x = 1
    return x

code = f.__code__

print(code.co_filename)
print(code.co_name)
print(code.co_qualname)
print(code.co_firstlineno)
```该元数据支持:```text
tracebacks
debuggers
profilers
coverage tools
inspection
warnings
error locations
```如果没有源元数据,Python 仍然可以执行,但诊断会更糟糕。

## 23.18 线表和位置

代码对象将字节码偏移量映射回源位置。

您可以检查职位:```python
def f(x):
    return x + 1

for item in f.__code__.co_positions():
    print(item)
```此源映射支持精确的回溯和调试。

较旧的 CPython 版本使用不同的行号表格式。现代版本公开了更丰富的位置信息,包括列偏移。

工具应该更喜欢公共方法而不是直接解析私有二进制表。

## 23.19 异常表

现代 CPython 代码对象包含异常表信息。

异常表描述哪些字节码范围受异常处理程序保护。

例子:```python
def f():
    try:
        risky()
    except ValueError:
        recover()
```字节码本身是不够的。解释器还需要元数据,例如:```text
protected instruction range
handler target
stack depth restoration information
handler kind
```该元数据让 CPython 可以实现`try`, `except`, `finally`,以及相关的控制流程。

## 23.20 代码对象创建自`compile()`这`compile()`内置创建代码对象。

例子:```python
code = compile("x = 1\n", "<input>", "exec")

ns = {}
exec(code, ns)

print(ns["x"])
```对于表达式:```python
code = compile("1 + 2", "<input>", "eval")

print(eval(code))
```对于 AST 输入:```python
import ast

tree = ast.parse("x = 1\n")
code = compile(tree, "<ast>", "exec")

compile()运行与文件相同的广泛管道:```text source or AST ↓ validation ↓ symbol analysis ↓ bytecode generation ↓ code object


代码对象可以通过以下方式执行`exec()`或者`eval()`。

例子:```python
code = compile("x = 10\n", "<input>", "exec")

globals_dict = {}
locals_dict = {}

exec(code, globals_dict, locals_dict)

print(locals_dict["x"])

exec()提供运行时命名空间。

代码对象本身不包含这些命名空间。

对于表达式代码:```python code = compile("x + 1", "", "eval")

print(eval(code, {"x": 41}))


## 23.22 代码对象和框架

框架是代码对象的运行实例。

代码对象:```text
immutable compiled instructions
```框架:```text
current execution state
instruction pointer
local variables
evaluation stack
block state
globals
builtins
exception state
```例子:```python
def f(x):
    y = x + 1
    return y
```每次致电`f`创建或使用执行框架`f.__code__`

多个调用使用相同的代码对象但不同的帧状态。```text
f.__code__
    shared by all calls

frame for f(1)
    x = 1
    y = 2

frame for f(10)
    x = 10
    y = 11
```## 23.23 代码对象和`marshal`CPython 可以序列化代码对象`marshal`。

已编译`.pyc`文件包含编组代码对象和标头元数据。

这是特定于实现的。这`marshal`格式不是稳定的通用序列化格式。

实用规则:```text
use pickle or another format for application data
use marshal only for CPython internals or closely related tooling
```Python导入模块时,CPython可能会加载缓存的`.pyc`文件,读取代码对象并执行它,而不是重新编译源代码。

## 23.24 中的代码对象`.pyc`文件

一个`.pyc`文件存储编译后的字节码缓存数据。

从概念上讲:```text
.pyc file
    header
        magic number
        invalidation metadata
    marshalled module code object
```模块代码对象可以包含嵌套函数和类体代码对象`co_consts`

字节码缓存通过避免在源未更改时重复解析和编译来加速启动。

`.pyc`文件是特定于版本的。字节码和编组格式可以在 CPython 版本之间发生变化。

## 23.25 代码对象安全边界

代码对象是可执行数据。

运行代码对象相当于运行代码。

例子:```python
code = compile("import os; os.remove('file.txt')", "<input>", "exec")
exec(code)
```代码对象不会变得安全,因为它已经被编译了。

安全敏感系统不应执行不受信任的代码对象、不受信任的源或不受信任的编组字节码。

编译不是沙箱。

## 23.26 代码对象自省

代码对象对于内省很有用。

例子:```python
def f(a, b=1):
    c = a + b
    return c

code = f.__code__

for name in [
    "co_argcount",
    "co_posonlyargcount",
    "co_kwonlyargcount",
    "co_nlocals",
    "co_stacksize",
    "co_flags",
    "co_consts",
    "co_names",
    "co_varnames",
    "co_freevars",
    "co_cellvars",
]:
    print(name, getattr(code, name))
```这支持以下工具:```text
debuggers
profilers
coverage tools
tracers
decorators
test frameworks
bytecode inspectors
static analysis helpers
```## 23.27 代码对象替换

可以使用以下命令复制代码对象并进行修改`replace()`

例子:```python
def f():
    return 1

new_code = f.__code__.replace(co_name="g")
```这对于高级工具很有用。

但是改变代码对象的内部结构很容易违反假设。例如,字节码、常量、名称、堆栈大小、标志和异常表必须保持一致。

损坏的代码对象可能会使工具崩溃、引发令人困惑的错误或行为不正确。

使用`replace()`用于元数据编辑或仔细验证的字节码工作。

## 23.28 代码对象是特定于版本的

代码对象字段和字节码详细信息随 Python 版本的不同而变化。

版本敏感区域的示例:```text
opcode names
opcode arguments
inline cache layout
exception table format
line table format
code object constructor signature
optimization behavior
```强大的工具应该使用公共 API```text
dis
inspect
types.CodeType.replace
co_positions()
co_lines()
ast
compile
```避免假设原始字节码布局保持稳定。

## 23.29 CPython 源代码区域

重要的 CPython 源文件包括:```text
Include/cpython/code.h
Objects/codeobject.c
Python/compile.c
Python/assemble.c
Python/flowgraph.c
Python/marshal.c
Lib/dis.py
Lib/inspect.py
Lib/types.py
```概念角色:

|面积 |角色 |
| -------------- | ---------------------------------- |
|`code.h`|代码对象结构定义|
|`codeobject.c`|代码对象的创建和行为|
|`compile.c`| AST 到指令生成 |
|`assemble.c`|最终代码对象组装 |
|`flowgraph.c`|控制流图处理 |
|`marshal.c`|字节码缓存的序列化 |
|`dis.py`|人类可读的字节码检查 |

## 23.30 最小心智模型

使用这个模型:```text
A code object is immutable compiled Python code.
It contains bytecode plus metadata.
A function object wraps a code object with runtime context.
A frame executes a code object.
Nested functions, lambdas, classes, comprehensions, and generators have nested code objects.
Constants, names, locals, free variables, and cell variables are stored in code object tables.
The interpreter uses the code object to allocate frames and run bytecode.
```代码对象是将 CPython 的编译器连接到其虚拟机的编译工件。