24. 常量、名称和局部变量

代码对象不会将 Python 源代码存储为文本。它存储紧凑表和通过索引引用这些表的字节码指令。

最重要的三个表是:```text id="mx2a9w" co_consts co_names co_varnames


对于这个函数:```python id="wg80pj"
def f(a):
    b = len(a)
    return b + 1
```CPython 存储:```text id="jxh1ou"
constants:
    None
    1

names:
    len

local variables:
    a
    b
```然后字节码指令通过索引引用这些条目。

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

常量、名称和局部变量在编译期间组装。```text id="la84x8"
AST
    
symbol table
    
compiler
    
instruction stream
    
constant table
    
name table
    
local variable table
    
code object
```这些表是代码对象的一部分。

解释器在执行字节码时使用它们。```text id="gvqxes"
LOAD_CONST 1
LOAD_GLOBAL 0
LOAD_FAST 0
STORE_FAST 1
```每个数字操作数都索引到代码对象的表之一中。

## 24.2 为什么代码对象使用表

字节码需要紧凑。

CPython 不是将完整的 Python 对象或字符串直接嵌入到每条指令中,而是将它们存储在表中一次。

例子:```python id="y6ymck"
def f():
    print("hello")
    print("hello")
```字符串`"hello"`可以出现一次`co_consts`

名称`"print"`可以出现一次`co_names`

字节码通过索引引用它们。

从概念上讲:```text id="y1yh5e"
co_consts:
    0: None
    1: "hello"

co_names:
    0: "print"

bytecode:
    LOAD_GLOBAL 0       print
    LOAD_CONST 1        "hello"
    CALL
    POP_TOP

    LOAD_GLOBAL 0       print
    LOAD_CONST 1        "hello"
    CALL
    POP_TOP
```这减少了重复并为解释器提供了稳定的查找位置。

## 24.3 常量

常量是存储在中的文字值和编译工件`co_consts`

常见常量包括:```text id="0sqmi6"
None
True
False
integers
floats
complex numbers
strings
bytes
tuples of constants
frozensets of constants
nested code objects
```例子:```python id="0bigls"
def f():
    return 123, "abc", None
```检查:```python id="unotfl"
print(f.__code__.co_consts)
```典型形状:```text id="zsqktw"
(None, 123, 'abc')
```函数返回的元组本身可以由常量构造,或者如果编译器可以安全地折叠它,则可以将其存储为常量元组。

## 24.4 的`None`常数

大多数代码对象包括`None``co_consts`

例子:```python id="mxsc3g"
def f():
    x = 1
```尽管没有明确的来源`return None`,函数返回`None`隐含地。

编译器必须能够发出:```text id="b7i1tf"
LOAD_CONST None
RETURN_VALUE
```在模块级别,CPython 也经常发出最终的`RETURN_VALUE``None`完成执行。

## 24.5 数字常量

数字文字通常出现在`co_consts`

例子:```python id="y88hr2"
def f():
    return 10 + 20
```AST 可能包含`10 + 20`,但编译器可能会将其折叠为`30`

检查:```python id="y7a6vi"
import dis

def f():
    return 10 + 20

print(f.__code__.co_consts)
dis.dis(f)
```你可能只看到`30`作为加载常量。

恒定折叠是保守的。 CPython 可以折叠在编译时安全且确定的表达式。它无法折叠具有运行时效果的表达式。

例子:```python id="3nempa"
def f():
    return 10 + x

x在编译时未知,因此它不能成为常量。

24.6 字符串和字节常量

字符串和字节文字出现在co_consts

例子:python id="086py8" def f(): return "hello", b"world" 检查:python id="mwit9a" print(f.__code__.co_consts) 相邻的字符串文字可以在编译期间连接:```python id="yn2nfn" def f(): return "hello" "world"


结果是一个常数:```text id="g8bxm7"
"helloworld"
```## 24.7 元组常量

仅包含常量的元组可以存储为常量。

例子:```python id="u33byh"
def f():
    return (1, 2, 3)
```编译器可能会存储整个元组:```text id="kw4dlg"
(1, 2, 3)
````co_consts`

但如果一个元素是动态的:```python id="f0psq9"
def f(x):
    return (1, x, 3)
```元组必须在运行时构建。

编译模式:```text id="0f7svq"
LOAD_CONST 1
LOAD_FAST x
LOAD_CONST 3
BUILD_TUPLE 3
```编译器将不可变常量容器与运行时构建的容器区分开来。

## 24.8 冻结集合常数

集合文字是可变的,因此集合本身不能是字节码中的常量。

CPython可能会使用`frozenset`优化成员资格测试的常量。

例子:```python id="a4bjga"
def is_small(x):
    return x in {1, 2, 3}
```源使用一组文字。编译器可以编译成员资格`frozenset`常量,因为该集合仅用于成员资格测试。

从概念上讲:```text id="y0dtbp"
LOAD_FAST x
LOAD_CONST frozenset({1, 2, 3})
CONTAINS_OP
```这可以避免每次函数运行时重建集合。

编译器必须保留语义。此优化对于常量有效,因为成员资格`{1, 2, 3}``frozenset({1, 2, 3})`对于相关操作给出相同的结果。

## 24.9 嵌套代码对象作为常量

嵌套函数将其代码对象存储在`co_consts`

例子:```python id="n17lr5"
def outer():
    def inner():
        return 1
    return inner
```检查:```python id="56jqwc"
for const in outer.__code__.co_consts:
    print(repr(const))
```您将找到一个代码对象`inner`

在运行时,外部函数执行加载嵌套代码对象并创建函数对象的字节码:```text id="xrarxf"
LOAD_CONST <code object inner>
MAKE_FUNCTION
STORE_FAST inner
```嵌套代码对象是常量编译数据。函数对象是在运行时创建的。

## 24.10 名称表`co_names`存储字节码操作使用的符号名称,这些符号名称不是快速局部变量或闭包变量。

这些包括:```text id="b9fx1t"
global names
builtin lookup names
attribute names
imported module names
method names
some class body names
```例子:```python id="sl10xc"
def f(xs):
    return len(xs)
```检查:```python id="il1b6j"
print(f.__code__.co_names)
```典型形状:```text id="6whwgp"
('len',)
```字节码加载`len`通过名称索引。

## 24.11 全局变量和内置函数

一个名字在`co_names`可以在运行时引用全局或内置。

例子:```python id="pxxc09"
def f(xs):
    return len(xs)

len不是本地的。它是使用全局查找规则加载的。

运行时查找检查:text id="d2z1ce" function globals then builtins 代码对象不存储实际的内置函数len。它存储名称字符串"len"

这意味着更改全局变量会影响执行:```python id="oghwkx" def f(xs): return len(xs)

len = lambda x: 999

print(f([1, 2, 3])) ```在该模块内部,len现在解析为全局 lambda,而不是内置的。

24.12 属性名称

属性名称也存在于co_names

例子:python id="trjd66" def f(obj): return obj.value 检查:python id="vrg5um" print(f.__code__.co_names) 典型形状:text id="133tbx" ('value',) 字节码使用:text id="f1yae8" LOAD_FAST obj LOAD_ATTR value 字符串"value"被存储一次co_names

这与全局查找是分开的。同一个表存储多个指令系列使用的名称字符串。

24.13 方法名称

方法调用语法也使用名称。

例子:```python id="pwnbfj" def f(obj): return obj.run()


`run`出现在`co_names`

根据Python版本,编译器可能会发出专门的面向调用的指令,但方法名称仍然来自名称表。

概念字节码:```text id="vjlnib"
LOAD_FAST obj
LOAD_METHOD run
CALL 0
```名称表存储方法名称字符串。

## 24.14 导入名称

导入也使用名称表。

例子:```python id="sf2lb3"
def f():
    import os
    return os.getcwd()
```名称表可能包含:```text id="pi43tc"
os
getcwd
```本地表包含`os`如果导入位于函数内部,因为导入绑定了局部变量。

这种区别很重要:```text id="hc8fxf"
co_names:
    names needed by import and attribute operations

co_varnames:
    local binding created by import os
```从局部变量布局的角度来看,导入是赋值。

## 24.15 局部变量`co_varnames`存储快速局部变量名称。

例子:```python id="nyh7qv"
def f(a, b):
    c = a + b
    return c
```检查:```python id="g0h75x"
print(f.__code__.co_varnames)
```典型形状:```text id="ja0vd0"
('a', 'b', 'c')
```框架将本地值存储在类似数组的布局中。字节码索引到此布局中。```text id="x503qw"
LOAD_FAST 0    a
LOAD_FAST 1    b
STORE_FAST 2   c
```这就是为什么本地访问比全局字典查找更快。

## 24.16 参数局部变量

函数参数首先出现在`co_varnames`

例子:```python id="9b48mv"
def f(a, b, c=0):
    d = a + b + c
    return d

co_varnames通常开始:```text id="83pbbd" ('a', 'b', 'c', 'd')


默认值存储在函数对象中,而不是直接存储在代码对象中。```python id="9q7w3m"
print(f.__defaults__)
print(f.__code__.co_varnames)
```## 24.17 仅位置和仅关键字局部变量

代码对象分别存储参数计数`co_varnames`

例子:```python id="87asnp"
def f(a, b, /, c, *, d):
    return a, b, c, d
```检查:```python id="kbjcbq"
code = f.__code__

print(code.co_posonlyargcount)
print(code.co_argcount)
print(code.co_kwonlyargcount)
print(code.co_varnames)
```本地名称存储在`co_varnames`,而计数定义如何解释前导条目。

从概念上讲:```text id="h79uky"
co_varnames:
    a, b, c, d

co_posonlyargcount:
    2

co_argcount:
    3

co_kwonlyargcount:
    1
```调用机制使用此元数据进行参数绑定。

## 24.18`*args`和`**kwargs`变量参数名称也出现在`co_varnames`。

例子:```python id="a78joh"
def f(a, *args, **kwargs):
    return args, kwargs
```检查:```python id="57zit6"
code = f.__code__

print(code.co_varnames)
print(code.co_flags)
````co_flags`字段记录该函数接受可变位置或关键字参数。

名字`args``kwargs`是本地插槽。

## 24.19 临时值未命名为局部变量

计算堆栈保存临时值。

例子:```python id="yqtwg0"
def f(a, b, c):
    return a + b * c

co_varnames包含:text id="gvos5r" a b c 它不包含临时中间值,例如b * c

该中间结果存在于框架的评估堆栈中。

概念堆栈:```text id="3snx1b" LOAD_FAST a stack: a LOAD_FAST b stack: a, b LOAD_FAST c stack: a, b, c BINARY_OP * stack: a, temp BINARY_OP + stack: result RETURN_VALUE stack empty


## 24.20 单元格变量`co_cellvars`存储由嵌套函数捕获的局部变量。

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

    def inner():
        return x

    return inner
```检查:```python id="91ti0b"
print(outer.__code__.co_cellvars)
```典型结果:```text id="n2byuv"
('x',)
```里面`outer`, `x`必须生活在细胞中,因为`inner`之后可以访问它`outer`返回。

局部变量成为堆支持的闭包存储。

## 24.21 自由变量`co_freevars`存储从封闭范围捕获的名称。

使用相同的示例:```python id="b3corm"
inner = outer()
print(inner.__code__.co_freevars)
```典型结果:```text id="i1hd7z"
('x',)
```里面`inner`, `x`不是一个正常的当地人。它是从封闭单元加载的。

概念字节码:```text id="o8g8eg"
LOAD_DEREF x
RETURN_VALUE
```代码对象记录了名称。函数对象携带实际的单元格。

## 24.22 当地人词典

在正常的优化函数内,局部变量