“is” operator behaves unexpectedly with integers
为什么下面的行为在Python中出乎意料?
1 2 3 4 5 6 7 8 9 10 | >>> a = 256 >>> b = 256 >>> a is b True # This is an expected result >>> a = 257 >>> b = 257 >>> a is b False # What happened here? Why is this False? >>> 257 is 257 True # Yet the literal numbers compare properly |
我使用的是python 2.5.2。在尝试一些不同版本的Python时,Python2.3.3显示了上述99到100之间的行为。
基于以上,我可以假设python是在内部实现的,这样"小"整数以不同于大整数的方式存储,并且
看看这个:
1 2 3 4 5 6 7 8 9 10 11 12 | >>> a = 256 >>> b = 256 >>> id(a) 9987148 >>> id(b) 9987148 >>> a = 257 >>> b = 257 >>> id(a) 11662816 >>> id(b) 11662828 |
编辑:下面是我在python 2文档中找到的,"纯整数对象"(对于python 3来说是一样的):
The current implementation keeps an
array of integer objects for all
integers between -5 and 256, when you
create an int in that range you
actually just get back a reference to
the existing object. So it should be
possible to change the value of 1. I
suspect the behaviour of Python in
this case is undefined. :-)
Python's"is" operator behaves unexpectedly with integers?
总之,我要强调的是:不要使用
这不是你应该期待的行为。
相反,使用
1 2 3 4 5 6 7 | >>> a = 1000 >>> a == 1000 # Test integers like this, True >>> a != 5000 # or this! True >>> a is 1000 # Don't do this! - Don't use `is` to test integers!! False |
解释
要了解这一点,您需要了解以下内容。
首先,
The operators
is andis not test for object identity:x is y is true
if and only if x and y are the same object.x is not y yields the
inverse truth value.
所以下面是等价的。
1 2 | >>> a is b >>> id(a) == id(b) |
从文档中:
id
Return the"identity" of an object. This is an integer (or long
integer) which is guaranteed to be unique and constant for this object
during its lifetime. Two objects with non-overlapping lifetimes may
have the sameid() value.
注意,cpython(Python的引用实现)中的对象ID是内存中的位置,这是一个实现细节。其他的python实现(如jython或ironpython)很容易对
那么,
Comparisons to singletons like
None should always be done withis or
is not , never the equality operators.
问题
您可以询问并说明以下问题(带代码):
Why does the following behave unexpectedly in Python?
1
2
3
4 >>> a = 256
>>> b = 256
>>> a is b
True # This is an expected result
这不是预期的结果。为什么要这样?它只意味着由
但也许我们应该高兴的是,每当我们声明一个等于256的值时,内存中就没有一个新的独立实例。
1
2
3
4 >>> a = 257
>>> b = 257
>>> a is b
False # What happened here? Why is this False?
看起来我们现在在内存中有两个值为
1
2 >>> 257 is 257
True # Yet the literal numbers compare properly
好吧,这看起来像是您的特定的python实现试图变得智能,除非必须这样做,否则不要在内存中创建冗余值的整数。您似乎表明您正在使用python的引用实现,即cpython。对CPython有好处。
如果cpython能够在全球范围内做到这一点,如果它能够以如此低的成本做到这一点(就像查找过程中的成本一样),也许另一个实现可能会更好。
但是对于代码的影响,您不应该关心整数是否是整数的特定实例。您应该只关心该实例的值是什么,并为此使用常规的比较运算符,即
1 | >>> a is b |
是一样的
1 | >>> id(a) == id(b) |
那我们为什么要用
这可以是一个非常快速的检查,例如,检查两个非常长的字符串的值是否相等。但由于它适用于对象的唯一性,因此我们对它的用例有限。实际上,我们主要想用它来检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | SENTINEL_SINGLETON = object() # this will only be created one time. def foo(keyword_argument=None): if keyword_argument is None: print('no argument given to foo') bar() bar(keyword_argument) bar('baz') def bar(keyword_argument=SENTINEL_SINGLETON): # SENTINEL_SINGLETON tells us if we were not passed anything # as None is a legitimate potential argument we could get. if keyword_argument is SENTINEL_SINGLETON: print('no argument given to bar') else: print('argument to bar: {0}'.format(keyword_argument)) foo() |
哪些印刷品:
1 2 3 4 | no argument given to foo no argument given to bar argument to bar: None argument to bar: baz |
因此,我们看到,使用
这取决于你想看两个东西是相等的还是相同的。
1 2 3 4 5 6 | In [29]: a = 3 In [30]: b = 3 In [31]: id(a) Out[31]: 500729144 In [32]: id(b) Out[32]: 500729144 |
您应该使用
型
我迟到了,但你想知道你的答案吗?*
关于cpython的好处是你可以看到它的来源。现在我将使用
在cpython中,处理创建新
The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)
号
不知道你的情况,但我看到了,我想:我们去找那个阵列吧!
如果您没有摆弄实现cpython的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
现在,我们不是
1 2 3 4 | #define CHECK_SMALL_INT(ival) \ do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \ return get_small_int((sdigit)ival); \ } while(0) |
号
因此,如果值
1 | if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) |
那么,什么是
1 2 3 4 5 6 | #ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif |
。
所以我们的条件是
除了继续我们的旅程,没有其他地方可以去,我们可以从它的辉煌中(好吧,我们只是看看它的身体,因为这是有趣的事情):
1 2 3 4 | PyObject *v; assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS); v = (PyObject *)&small_ints[ival + NSMALLNEGINTS]; Py_INCREF(v); |
好的,声明一个
1 | v = (PyObject *)&small_ints[ival + NSMALLNEGINTS]; |
。
1 2 3 4 5 6 | /* 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]; |
。
是的,这是我们的人。当您想在
由于引用的是同一对象,因此直接发出
但是,它们是什么时候分配的呢?是吗?
在
1 2 3 | for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) { // Look me up! } |
我希望我的解释使你现在清楚地了解了(双关语显然是有意的)。
但是,257是257吗?怎么了?
这实际上更容易解释,我已经尝试过这样做;这是因为python将执行这个交互式语句:
1 | >>> 257 is 257 |
。
作为一个整体。在编制本声明的过程中,cpython将看到您有两个匹配的文本,并将使用同一个表示
1 2 3 | >>> codeObj = compile("257 is 257","blah!","exec") >>> codeObj.co_consts (257, None) |
当cpython执行操作时,它现在只加载完全相同的对象:
1 2 3 4 5 | >>> import dis >>> dis.dis(codeObj) 1 0 LOAD_CONST 0 (257) # dis 3 LOAD_CONST 0 (257) # dis again 6 COMPARE_OP 8 (is) |
号
因此,
>--我将尝试以更具介绍性的方式来表达这一点,以便大多数人能够跟随。
正如您可以签入源文件intobject.c一样,python缓存小整数以提高效率。每次创建对小整数的引用时,都会引用缓存的小整数,而不是新对象。257不是一个小整数,因此它是作为另一个对象计算的。
因此最好使用
我认为你的假设是正确的。用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | In [1]: id(255) Out[1]: 146349024 In [2]: id(255) Out[2]: 146349024 In [3]: id(257) Out[3]: 146802752 In [4]: id(257) Out[4]: 148993740 In [5]: a=255 In [6]: b=255 In [7]: c=257 In [8]: d=257 In [9]: id(a), id(b), id(c), id(d) Out[9]: (146349024, 146349024, 146783024, 146804020) |
似乎数字
对于不可变的值对象,例如整数、字符串或日期时间,对象标识并不是特别有用。最好考虑平等。标识本质上是值对象的实现细节——因为它们是不可变的,所以对同一对象或多个对象具有多个引用之间没有有效的区别。
另一方面,php的
1 2 3 | class Unequal: def __eq__(self, other): return False |
PHP显然允许"内置"类使用相同的功能(我认为这是指在C级别实现,而不是在PHP中实现)。稍微不那么荒谬的用法可能是计时器对象,它每次用作数字时都有不同的值。我不知道你为什么要模仿VisualBasic的
格雷格·休吉尔(GregHewgill)做了一个澄清的评论:"我的目标是比较对象的同一性,而不是价值的平等。除了数字之外,我希望在这里将对象标识视为值相等。"
这将是另一个答案,因为我们必须将事物归类为数字,以选择我们是与
我们可以尝试在已知的所有数字类型中使用
1 2 3 | import numpy, numbers assert not issubclass(numpy.int16,numbers.Number) assert issubclass(int,numbers.Number) |
顺便说一下,numpy将生成单独的低数字实例。
我真的不知道这个问题变种的答案。我想理论上可以用ctypes来调用
最后,这个问题源于python最初没有带有诸如scheme的
字符串也会发生这种情况:
1 2 3 | >>> s = b = 'somestr' >>> s == b, s is b, id(s), id(b) (True, True, 4555519392, 4555519392) |
现在一切都好了。
1 2 3 4 | >>> s = 'somestr' >>> b = 'somestr' >>> s == b, s is b, id(s), id(b) (True, True, 4555519392, 4555519392) |
这也是意料之中的。
1 2 3 4 5 6 7 8 | >>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a' >>> s1 == b1, s1 is b1, id(s1), id(b1) (True, True, 4555308080, 4555308080) >>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a' >>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a' >>> s1 == b1, s1 is b1, id(s1), id(b1) (True, False, 4555308176, 4555308272) |
这是出乎意料的。
还有一个问题没有在任何现有答案中指出。python可以合并任意两个不可变的值,而预先创建的小int值并不是实现这一点的唯一方法。一个Python实现永远不能保证做到这一点,但它们所做的不仅仅是小整数。
一方面,还有一些其他预先创建的值,例如空的
1 2 3 4 | >>> a = () >>> b = () >>> a is b True |
但是,即使是未预先创建的值也可以相同。考虑这些例子:
1 2 3 4 5 6 7 | >>> c = 257 >>> d = 257 >>> c is d False >>> e, f = 258, 258 >>> e is f True |
这不仅限于
1 2 3 | >>> g, h = 42.23e100, 42.23e100 >>> g is h True |
显然,cpython没有为
cpython编译器将把一些已知的不可变类型(如
你可以通过分解字节码看到发生了什么。试着定义一个执行
1 2 3 4 5 6 7 8 9 10 11 12 | >>> def f(): i, j = 258, 258 >>> dis.dis(f) 1 0 LOAD_CONST 2 ((128, 128)) 2 UNPACK_SEQUENCE 2 4 STORE_FAST 0 (i) 6 STORE_FAST 1 (j) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE >>> f.__code__.co_consts (None, 128, (128, 128)) >>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1]) 4305296480, 4305296480, 4305296480 |
您可能会注意到编译器已经将
1 2 3 | >>> k, l = (1, 2), (1, 2) >>> k is l False |
把它放到一个函数中,
还有一个CPython做的优化:字符串实习生。与编译器常量折叠不同,这不局限于源代码文本:
1 2 3 4 | >>> m = 'abc' >>> n = 'abc' >>> m is n True |
另一方面,它仅限于
无论如何,对于值必须是、可能是或不能是不同的规则,在不同的实现之间、同一个实现的版本之间、甚至在同一个实现的同一副本上运行同一代码之间,都会有所不同。
为了取乐,学习特定Python的规则是值得的。但是在代码中依赖它们是不值得的。唯一安全的规则是:
- 不要编写假定两个相等但分别创建的不可变值相同的代码。
- 不要编写假定两个相等但分别创建的不可变值是不同的代码。
或者,换句话说,只使用
型
看看这里
The current implementation keeps an array of integer objects for all
integers between -5 and 256, when you create an int in that range you
actually just get back a reference to the existing object.
号