在阅读了与我的问题非常相似的this和this之后,我仍然无法理解以下行为:
a = 257
b = 257
print(a is b) #False
a, b = 257, 257
print(a is b) #True
打印id(a)
和id(b)
时,我可以看到在单独的行中分配值的变量具有不同的ID,而使用多个赋值时,两个值具有相同的ID:
a = 257
b = 257
print(id(a)) #139828809414512
print(id(b)) #139828809414224
a, b = 257, 257
print(id(a)) #139828809414416
print(id(b)) #139828809414416
但是,通过说多个相同值的赋值总是会创建指向同一个id的指针来解释这种行为是不可能的:
a, b = -1000, -1000
print(id(a)) #139828809414448
print(id(b)) #139828809414288
是否有明确的规则,它解释了变量何时变为id
而不是?
修改
相关信息:此问题中的代码以交互模式(ipython3)
运行答案 0 :(得分:3)
这是因为在加载常量值期间UNPACK_SEQUENCE
时的pythons解释器优化。当python在解包期间遇到一个iterable时,它不会多次加载重复的对象,而只是保留第一个对象并将所有重复的变量名分配给一个指针(在CPython实现中)。因此,所有变量都将成为对一个对象的相同引用。在python级别,您可以将此行为视为使用字典作为不保留重复键的命名空间。
换句话说,解压缩将等同于以下命令:
a = b = 257
关于负数,在python 2.X中它没有任何区别但是在python 3.X中似乎对于小于-5的数字python将在解包期间创建新对象:
>>> a, b = -6, -6
>>> a is b
False
>>> a, b = -5, -5
>>>
>>> a is b
True
答案 1 :(得分:2)
这是由于字节码编译器中的常量折叠优化。当字节码编译器编译一批语句时,它uses a dict来跟踪它所看到的常量。这个dict会自动合并任何等效的常量。
这是负责记录和编号常数的例程(以及一些相关的职责):
static int
compiler_add_o(struct compiler *c, PyObject *dict, PyObject *o)
{
PyObject *t, *v;
Py_ssize_t arg;
t = _PyCode_ConstantKey(o);
if (t == NULL)
return -1;
v = PyDict_GetItem(dict, t);
if (!v) {
arg = PyDict_Size(dict);
v = PyInt_FromLong(arg);
if (!v) {
Py_DECREF(t);
return -1;
}
if (PyDict_SetItem(dict, t, v) < 0) {
Py_DECREF(t);
Py_DECREF(v);
return -1;
}
Py_DECREF(v);
}
else
arg = PyInt_AsLong(v);
Py_DECREF(t);
return arg;
}
您可以看到它只添加了一个新条目,并且如果找不到已存在的等效常量,则分配一个新数字。 (_PyCode_ConstantKey
位可确保0.0
,-0.0
和0
等内容被视为不等价。)
在交互模式下,每次解释器必须实际运行命令时批处理结束,因此在命令之间不会发生常量折叠:
>>> a = 1000
>>> b = 1000
>>> a is b
False
>>> a = 1000; b = 1000 # 1 batch
>>> a is b
True
在脚本中,所有顶级语句都是一个批处理,因此more constant folding happens:
a = 257
b = 257
print a is b
在脚本中,这会打印True
。
函数的代码与函数外部的代码分开跟踪其常量,这限制了常量折叠:
a = 257
def f():
b = 257
print a is b
f()
Even in a script,打印False
。
答案 2 :(得分:0)
任何此类规则都是特定于实现的。例如,CPython为小整数(-5到256)预分配int
个对象作为性能优化。
唯一的一般规则是假设任何文字的使用都会生成一个新对象。