我来晚了,但你需要一些源代码来解答吗? 我会尝试用简单易懂的方式表达,以便更多人能够理解。
CPython的一个好处是你可以看到它的源代码。我将使用
3.5版本的链接,但找到相应的
2.x版本非常简单。
在CPython中,处理创建新的
int
对象的
C-API函数是
PyLong_FromLong(long v)
。这个函数的描述是:
当前实现为所有介于-5和256之间的整数保留了一个整数对象数组,当你在该范围内创建一个int时,你实际上只会得到对现有对象的引用。因此更改1的值应该是可能的。我怀疑在这种情况下Python的行为是未定义的。:-)
(我的斜体)
不知道你怎么看,但我看到这个想法:“让我们找到那个数组!”
如果你还没有尝试过修改实现CPython的C代码,那么你应该尝试一下。这里面所有的东西都很有组织并且易于阅读。对于我们的情况,我们需要查看主源代码目录树中的Objects
子目录。
PyLong_FromLong
处理long
对象,所以我们应该很容易推断出我们需要查看longobject.c
文件。在查看内部后,您可能会认为事情很混乱;确实如此,但不用担心,我们要找的函数位于第230行等待我们查看它。这是一个较小的函数,因此主体部分(不包括声明)可以轻松粘贴在此处:
PyObject *
PyLong_FromLong(long ival)
{
CHECK_SMALL_INT(ival);
if (ival < 0) {
abs_ival = 0U-(unsigned long)ival;
sign = -1;
}
else {
abs_ival = (unsigned long)ival;
}
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代码大师,但我们也不傻,我们可以看出
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
和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
看起来很像我们一直在寻找的数组,实际上它就是!我们本可以读这该死的文档,早就知道了!:
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
好的,这是我们的内容。当你想要在[NSMALLNEGINTS, NSMALLPOSINTS)
范围内创建一个新的int
时,你将会得到一个已经预先分配好的对象的引用。
由于引用指向同一个对象,因此直接发出id()
或使用is
检查其身份将返回完全相同的结果。
但是,它们是什么时候分配的??
在_PyLong_Init
中的初始化期间,Python将很乐意为您进入一个for循环来完成这个过程:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
查看源代码以阅读循环体!
我希望我的解释现在让你更清楚地了解了C语言(明显是双关语)。
但是,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
。
is
的主要用例 - 不要使用它来测试整数、字符串、元组或其他类似的东西的相等性。然而,我正在尝试将一个简单的状态机集成到我的类中,由于状态是不透明的值,其唯一可观察的属性是相同或不同,因此使用is
进行比较看起来非常自然。我计划使用已经 interned 的字符串作为状态。我本来更喜欢使用普通整数,但不幸的是 Python 不能 intern 整数(0 is 0
是一个实现细节)。 - Alexeyis
的情况是处理浮点数 NaN:math.nan==math.nan
总是返回False
。 - Mark Ransomfloat
中使用is
,即使你正在检查NaN:float('nan') is float('nan')
返回False
,原因与int('257') is int('257')
相同:它们是不同的实例,尽管它们是无法区分的。 - Jasmijn