为什么python虚拟机有co_名称,而不只是使用co_常量?

为什么python虚拟机有co_名称,而不只是使用co_常量?,python,bytecode,vm-implementation,Python,Bytecode,Vm Implementation,Python编译器生成的代码对象包含指令中使用的常量元组(名为co_consts),以及包含名称的元组(名为co_names) 为什么有两个不同的列表?将co_consts用于名称不是更简单吗?考虑以下函数 def f(x): x += n return x * 4 此处x是一个本地名称,其值可以更改4是一个常数。它的价值永远不会改变。但是,它仍然是一个对象,最好缓存它们,而不是在每次需要时创建一个新对象。最后,n是一个全局引用。字符串“n”由函数存储,因此它可以用作从函数的全

Python编译器生成的代码对象包含指令中使用的常量元组(名为
co_consts
),以及包含名称的元组(名为
co_names


为什么有两个不同的列表?将
co_consts
用于名称不是更简单吗?

考虑以下函数

def f(x):
    x += n
    return x * 4
此处
x
是一个本地名称,其值可以更改<代码>4是一个常数。它的价值永远不会改变。但是,它仍然是一个对象,最好缓存它们,而不是在每次需要时创建一个新对象。最后,
n
是一个全局引用。字符串
“n”
由函数存储,因此它可以用作从函数的全局上下文检索
n
的键

>>> f.__code__.co_nlocals # just 1 (for x)
1
>>> f.__code__.co_consts
(None, 4)
>>> f.__code__.co_names
('n',)
>>> "n" in f.__globals__ and globals() is f.__globals__
True
将名称和常量分开的原因是为了内省。合并元组的唯一真正原因是内存效率,尽管这只会为每个函数获得一个对象和一个指针。考虑下面的函数。

def g():
    return "s" * n

如果包含常量的元组与包含名称的元组合并,那么您(而不是VM)将无法分辨哪些值用于访问全局变量,哪些值是函数的常量。

我知道这个答案已经过时11个月了,但通过我的修补,似乎发生了以下情况

def g():
    return "s" * n
要访问字节码中的co_名称,可以使用LOAD_GLOBAL(co名称索引),这会将对所需co_名称的引用推送到堆栈上,例如它的间接引用

要访问字节码中的co_常量,可以使用LOAD_CONST(co consts index),这会将存储在所需co_常量处的实际值推送到堆栈上,例如它的直接


我不确定它是否与python级别有任何直接关系,但在字节码级别,这是一个深刻的区别

只是为了检查一下,你说的是cpython,对吗?哪个版本?@GamesBrainiac:
co_name
在cpython 2和3以及pypy中都使用。我不确定其他实现,因为我没有使用它们。我记不太清楚,但我很确定它与酸洗有关。当然,这有很大的区别,因为
LOAD_CONST
在堆栈上加载常量,而
LOAD_GLOBAL
则执行查找。但是,全局名称只是一个Python
str
对象,可以放在
co_consts
列表中,而不是
co_names
。我的问题是“为什么只使用
co_const
一个列表就足够了,而使用两个列表?”。。。我不是说不需要
LOAD\u GLOBAL
opcode,因为我们有
LOAD\u CONST
,而是说这两个可以使用相同的列表,而不是有两个列表。任何引用名称的操作码都可以使用
op_consts
。我不知道它这样设计的原因,但这两条指令将不同类型的数据推送到堆栈上(值与引用)。如果您确实合并了consts和名称,您仍然需要某种指令来告诉VM它是引用还是值,因此,每个加载有2条指令,例如loadconstname、setconstnametype,这比一个调用要快,而且如果您的idjut(例如me)与字节码混在一起,那么从VM的角度来看更安全