我迟到了,但是你想知道答案的来源吗?我会尝试以介绍性的方式说出这个,以便更多人可以跟进。
CPython 的一个好处是您实际上可以看到它的源代码。我将使用 3.5 版本的链接,但找到相应的 2.x 链接很简单。
在 CPython 中,处理创建新 int 对象的 C-API 函数是 PyLong_FromLong(long v)。这个函数的描述是:
当前的实现为所有介于 -5 和 256 之间的整数保留一个整数对象数组,当您在该范围内创建一个 int 时,实际上您只是返回对现有对象的引用。所以应该可以改变 1 的值。我怀疑 Python 在这种情况下的行为是未定义的。 :-)
(我的斜体)
不了解你,但我看到这个并想:让我们找到那个数组!
如果你还没有摆弄过实现 CPython 的 C 代码你应该;一切都井井有条,可读性强。对于我们的案例,我们需要查看main source code directory tree 的Objects subdirectory。
PyLong_FromLong 处理long 对象,因此不难推断我们需要查看longobject.c 内部。往里看后,你可能会认为事情很混乱;他们是,但不要害怕,我们正在寻找的功能在line 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); 诱人地偷看我们;我们可以理解这与此有关。 Let's check it out:
#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 和NSMALLPOSINTS 是什么?宏! Here they are:
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
所以我们的条件是if (-5 <= ival && ival < 257) call get_small_int。
接下来让我们看看get_small_int in all its glory(好吧,我们只看它的主体,因为那是有趣的地方):
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 看起来很像我们一直在寻找的那个数组,它就是! We could've just read the damn documentation and we would've know all along!:
/* 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, NSMALLPOSINTS) 范围内创建一个新的int 时,您只需取回对已预先分配的现有对象的引用。
由于引用指向同一个对象,直接发出id() 或使用is 检查身份将返回完全相同的内容。
但是,它们是什么时候分配的??
During initialization in _PyLong_InitPython 很乐意进入 for 循环为您执行此操作:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
查看源代码以读取循环体!
我希望我的解释现在已经让你 C 清楚了事情(双关语显然是有意的)。
但是,257 is 257?怎么了?
这个其实更容易解释,and I have attempted to do so already;这是因为 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。