24.常量、名称和局部变量
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 当地人词典
在正常的优化函数内,局部变量