我迟到了,但你想要一些答案来源吗?我将试着以一种介绍性的方式来描述,这样更多的人可以跟上。
CPython的一个好处是你可以看到它的源代码。我将使用3.5版本的链接,但要找到对应的2。X个1是微不足道的。
在CPython中,处理创建新int对象的C-API函数是PyLong_FromLong(long v)。该函数的描述如下:
当前的实现为-5到256之间的所有整数保留了一个整数对象数组,当你在这个范围内创建一个int时,你实际上只是得到了对现有对象的引用。所以应该可以改变1的值。我怀疑Python在这种情况下的行为是未定义的。: -)
(斜体)
不知道你怎么想,但我看到这个就想:让我们找到那个数组!
如果你没有摆弄过实现CPython的C代码,你应该;一切都很有条理,可读。对于我们的例子,我们需要查看主源代码目录树的Objects子目录。
PyLong_FromLong处理的是长对象,因此不难推断我们需要查看longobject.c内部。在看了里面之后,你可能会认为一切都很混乱;它们是,但不用担心,我们要找的函数在第230行等待我们检查。它是一个较小的函数,所以主体(不包括声明)可以很容易地粘贴在这里:
PyObject *
PyLong_FromLong(long ival)
{
// omitting declarations
CHECK_SMALL_INT(ival);
if (ival < 0) {
/* negate: cant write this as abs_ival = -ival since that
invokes undefined behaviour when ival is LONG_MIN */
abs_ival = 0U-(unsigned long)ival;
sign = -1;
}
else {
abs_ival = (unsigned long)ival;
}
/* Fast path for single-digit ints */
if (!(abs_ival >> PyLong_SHIFT)) {
v = _PyLong_New(1);
if (v) {
Py_SIZE(v) = sign;
v->ob_digit[0] = Py_SAFE_DOWNCAST(
abs_ival, unsigned long, digit);
}
return (PyObject*)v;
}
现在,我们不是C master-code-haxxorz但我们也不笨,我们可以看到CHECK_SMALL_INT(ival);诱惑地窥视我们所有人;我们可以理解它与此有关。让我们来看看:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
所以它是一个宏,如果值ival满足条件,就调用函数get_small_int:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
什么是NSMALLNEGINTS和nsmallpoints ?宏!他们是:
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
所以我们的条件是if (-5 <= ival && ival < 257)调用get_small_int。
接下来让我们看看get_small_int的所有荣耀(好吧,我们只看它的主体,因为有趣的东西在那里):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
好的,声明一个PyObject,断言前面的条件成立并执行赋值:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Small_ints看起来很像我们一直在搜索的数组,而且确实如此!我们只要看看那该死的文件就知道了!:
/* Small integers are preallocated in this array so that they
can be shared.
The integers that are preallocated are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
没错,这就是我们要找的人。当你想在[NSMALLNEGINTS, nsmallpoints)范围内创建一个新的int时,你只会得到一个已经存在的对象的引用,这个对象已经被预先分配了。
由于引用引用的是同一个对象,因此直接发出id()或检查与is的标识将返回完全相同的结果。
但是,什么时候分配呢?
在_PyLong_Init的初始化过程中,Python会很高兴地进入一个for循环来为你做这件事:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
查看源代码以阅读循环体!
我希望我的解释让你现在明白了事情(双关语)。
但是,257就是257?有什么事吗?
这实际上更容易解释,我已经尝试过这样做;这是因为Python会将这个交互语句作为一个单独的块执行:
>>> 257 is 257
在编译此语句期间,CPython将看到您有两个匹配的字面量,并将使用相同的PyLongObject表示257。如果你自己编译并检查它的内容,你可以看到这一点:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
当CPython执行这个操作时,它现在只是加载相同的对象:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
所以is会返回True。