我迟到了,但是,你想要一些你的答案的来源吗?我会尝试以介绍性的方式表达这一点,以便更多人可以跟进。
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 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)
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)
call 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
。