10

在阅读了与我的问题非常相似的thisthis之后,我仍然无法理解以下行为:

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)

4

3 回答 3

3

UNPACK_SEQUENCE这是因为在加载常量值期间,pythons 解释器的优化。当 python 在解包过程中遇到一个可迭代对象时,它不会多次加载重复的对象,而是只保留第一个对象并将所有重复的变量名分配给一个指针(在 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
于 2016-02-08T17:12:18.273 回答
2

这是由于字节码编译器中的常量折叠优化。当字节码编译器编译一批语句时,它使用一个 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

在脚本中,所有顶级语句都是一批,因此会发生更多的常量折叠

a = 257
b = 257
print a is b

在脚本中,这将打印True.

函数的代码将其常量与函数外部的代码分开跟踪,这限制了常量折叠:

a = 257

def f():
    b = 257
    print a is b

f()

即使在脚本中,也会打印False

于 2016-02-08T17:53:04.487 回答
0

任何此类规则都是特定于实现的。例如,CPython 为小整数(-5 到 256)预分配int对象作为性能优化。

唯一的一般规则是假设对文字的任何使用都会生成一个新对象。

于 2016-02-08T17:10:48.197 回答