我来晚了,但是,您想从中获得答案吗?我将尝试以介绍性的方式对此进行说明,以便更多的人可以跟进。
关于CPython的一件好事是您实际上可以看到其来源。我将使用3.5版本的链接,但是找到相应的2.x链接是微不足道的。
在CPython中,用于创建新对象的C-API函数int
为PyLong_FromLong(long v)
。此功能的说明是:
当前的实现为-5到256之间的所有整数保留一个整数对象数组,当您在该范围内创建int时,实际上实际上是返回对现有对象的引用。因此应该可以更改1的值。我怀疑在这种情况下Python的行为是不确定的。:-)
(我的斜体)
不了解您,但我看到了并想:让我们找到那个数组!
如果您还不熟悉实现CPython的C代码,则应 ; 一切都井井有条,可读性强。对于我们而言,我们需要在看Objects
子目录中的主源代码目录树。
PyLong_FromLong
处理long
对象,因此不难推断我们需要窥视内部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 主代码-haxxorz,但我们也不傻,我们可以看到这CHECK_SMALL_INT(ival);
一切诱人地窥视着我们。我们可以理解,这与此有关。让我们来看看:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
因此,get_small_int
如果值ival
满足条件,则它是一个调用函数的宏:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
那么什么是NSMALLNEGINTS
和NSMALLPOSINTS
?宏!他们在这里:
#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];
是的,这是我们的家伙。当您要int
在该范围内创建一个新[NSMALLNEGINTS, NSMALLPOSINTS)
对象时,您只需返回对已预先分配的现有对象的引用。
由于引用引用的是同一对象,因此id()
直接发布或检查其上的身份is
将返回完全相同的内容。
但是,什么时候分配它们?
在_PyLong_Init
Python 初始化期间,将很乐意进入for循环为您执行此操作:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
查看源代码以阅读循环体!
希望我的解释使您现在对C的认识清楚(很明显是故意的)。
但是,257 is 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
。