"是"操作者的行为意外地与整数
-
08-07-2019 - |
题
为什么下面的表现出乎意料在蟒蛇?
>>> 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
我使用Python2.5.2.尝试一些不同的版本的蟒蛇,它的出现,蟒蛇2.3.3示出上述行为之间的99和100。
基于上述,我可以推测,蟒蛇在内部实施这样的,"小"整数储存在一个不同的方式比较大的整数和 is
操作者可以告诉大的差异。为什么漏水的抽象?什么是更好的方式比较两个任意的对象,看看他们是否相同时我不事先知道他们是否是数字或没有?
解决方案
看看这样的:
>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828
编辑:这就是我了Python 2文档中找到,“平原Integer对象“(这对 Python 3中相同):
在当前实现保持一个 整数的对象数组所有 -5和256,当你之间的整数 创建在该范围内的int你 其实只是回到一个参考 现有对象。所以它应该是 可能改变1。我的价值 怀疑的Python的行为 这种情况下是不确定的。 : - )
其他提示
Python的“是”与运营商的整数意外的行为?
在总结 - 我要强调: 不要使用is
比较整数的
这是不是行为,你应该有任何期待。
相反,使用==
和!=
分别比较是否相等和不等。例如:
>>> 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
说明
要知道这一点,你需要知道以下几点。
首先,什么是is not
办?这是一个比较操作。从文档:
在运营商
x is y
和对象标识x is not y
试验:id
为真 当且仅当x和y是相同的对象。id()
产生了 逆真值。
和因此下面是等价的。
>>> a is b
>>> id(a) == id(b)
从文档:
<强>
None
强> 返回一个对象的“身份”。这是一个整数(或长 整数),其被保证是此对象独特和恒定 它的生命周期中。具有非重叠的寿命两个对象可 具有相同的值256
请注意以下事实:在CPython的(参考实现的Python)的对象的id是在存储器中的位置是一个实现细节。的Python的其他实施方式(例如Jython或IronPython的)可以很容易地对不同的实现a
那么,什么是用例的b
? PEP8描述:
比较像
257
单身应始终进行bar
或 <=>,从来没有相等运算。
问题
您问,和状态,以下问题(与代码):
为什么下面意外行为在Python?
>>> a = 256 >>> b = 256 >>> a is b True # This is an expected result
有不的一个预期的结果。为什么期待?它仅仅意味着整数价值<=>双方引用<=>和<=>是整数的同一个实例。整型在Python一成不变的,因此,他们无法改变。这应该不会对任何代码没有任何影响。它不应该被期待。它仅仅是一个实现细节。
不过,我们也许应该庆幸,我们每次状态的值等于256时没有在内存中一个新的单独的实例
>>> 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的特定实现正试图将智能和内存中没有创造价值冗余整数,除非它有。你似乎表明您使用的是指涉的Python实现,这是CPython的。适合CPython的。
这可能是即使CPython中能做到这一点从全球来看,如果能做到这么便宜更好(因为会在查找成本),也许还有另外的实现可能。
至于对代码的影响,你不应该关心的整数是一个整数的特定实例。您应该只关心该实例的值,你就可以使用正常的比较运营商所提供的,即<=>。
<=>能做什么
<=>将检查两个对象的<=>是相同的。在CPython的,所述<=>是在存储器中的位置,但它可能是在另一实现一些其他唯一识别号码。与代码重申这样:
>>> a is b
是相同的
>>> id(a) == id(b)
为什么我们要使用<=>呢?
这可以是一个非常快的检查相对于说,检查是否两个很长的字符串值相等。但是,因为它适用于对象的唯一性,因此,我们有有限的使用情况吧。事实上,我们主要是想用它来检查<=>,这是一个单独的(在内存中存在的在一个地方唯一的实例)。我们可以创建其他单身是否有潜力,他们混为一谈,我们可能与<=>检查,但这些都是比较少见的。下面是一个例子e.g(将在Python 2和3工作)。
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()
哪些打印:
no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz
因此,我们看到,与<=>和前哨,我们能够在<=>被称为不带参数,当它被称为与<=>区分。这些是主要的用例为<=> - DO 不用它来测试对于整数,字符串,元组,或其他这样的事情的平等
这取决于你是否正在寻找,看看两件事情是相等的,或同一对象。
is
检查,看它们是否相同的对象,不只是相等。小整数很可能指向相同的存储器位置的空间效率
In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144
您应该使用==
比较任意对象的平等。您可以指定与__eq__
的行为,并__ne__
属性。
我迟到了但你想要一些来源与你的答案吗?*
好事约CPython是,实际上,你可以看到源于此。我要使用的链接 3.5
释放;找到相应的 2.x
那些是微不足道的。
在CPython, C-API
功能处理创建一个新的 int
对象是 PyLong_FromLong(long v)
.说明这种功能是:
当前执行保持一系列整数的对象为所有的整数之间-5和256,在创建一个int在这一范围实际上,你刚刚得到一个参考现有的对象.因此,它应该是能够改变的价值为1。我怀疑的行为蟒蛇在这种情况是不确定的。:-)
不知道你怎么想但我看到这一点,并认为: 让我们找到那阵!
如果你还没摆弄的 C
代码实施CPython 你应该, 一切都是相当有组织和可读性。我们的情况下,我们需要看的 Objects/
目录 的 主要来源码录树.
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
主码-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
蟒蛇会很乐意进入一个循环做为你做这个:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
// Look me up!
}
我希望我解释了你 C
(双关语显然intented)的东西清楚了。
但是,257 257?怎么了?
这实际上是容易解释, 我已经试图这样做已经;这是由于这样的事实,蟒蛇会执行这种互动式声明:
>>> 257 is 257
作为一个单一的区块。在complilation的这一声明,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
.
*我会尝试这个词在一个更多的介绍性的方式,以便为最有可能跟随。
intobject.c正如可以检查在源文件 ,Python的缓存效率小整数。您创建一个小的整数参考每一次,你是指缓存的小整数,而不是一个新的对象。 257不是一个小的整数,因此它被计算为不同的对象。
这是更好地使用==
用于这一目的。
我觉得你的假设是正确的。实验id
(对象的身份):
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)
似乎<= 255
被视为文字和任何上面的数字是不同的处理!
有关不可变值对象,如整数,字符串或日期时间,对象标识不是特别有用。这是更好地思考平等。身份基本上是值对象的实现细节 - 因为他们是不可变的,有具有多个参到相同的对象或多个对象之间没有有效的差异
is
是 身份平等的操作人员(职能喜欢 id(a) == id(b)
);这只是两个相等的数字不一定是相同的对象。对于业绩原因,一些小整数发生 memoized 因此,他们往往是相同的(这可以做到的,因为它们是固定不变的).
PHP的 ===
操作员,另一方面,被描述为检查平等和类型: x == y and type(x) == type(y)
为每Paulo Freitas'的评论。这就足够了共同的数字,但从不同的 is
为类定义 __eq__
在一个荒谬的方式进行:
class Unequal:
def __eq__(self, other):
return False
PHP显然允许同样的事情"内在"类(其中我是否意味着实施在C级,不在PHP)。一个稍微不那么荒谬的使用可能是一个定时器的对象,其中有一个不同的价值每次使用它作为一个数字。相当你为什么想要效仿些基本的 Now
相反的表示,这是一个评估 time.time()
我不知道。
格雷格Hewgill(OP)做了一个澄清的评论"我的目标是为比较的对象的身份,而不是平等的价值。除了数字,在这里我想对象的身份同样作为平等的价值。"
这会还没有另一个答案,正如我们已经为进行分类的东西,因为数字或不选择我们是否比较与 ==
或 is
. CPython 定义 数量议定书》, 包括PyNumber_Check,但这不是可以从蟒蛇本身。
我们可以尝试使用 isinstance
与所有的多种类型我们知道的,但这将不可避免地是不完整的。这种模块包含一个StringTypes的清单,但没有NumberTypes.由于Python2.6,建立在数类有一个基类 numbers.Number
, 但它具有同样的问题:
import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)
通过这种方式, 顽固 会产生独立的实例低的数字。
我实际上并不知道一个答案,这个变形的问题。我想一个理论上可以使用ctypes叫 PyNumber_Check
, 但即使这一职能 已经辩论了, 和它的肯定不便携式。我们只需要少特别是关于什么我们测试。
在结束,这个问题源自Python原本不是具有类型的树谓喜欢 方案的 number?
, 或 Haskell 类型 Num. is
检查对象的身份,不值平等。PHP有一个丰富多彩的历史里 ===
显然的行为 is
唯一的对象 在PHP5,但是不。PHP4.这些都是增长的痛苦的移动,跨越语言(包括版本一个)。
还有另外一个问题,是不是指出在现有的任何答复。蟒蛇是允许合并的任何两个不可改变的价值,并预先创建的小int值不是唯一的方法,这可能发生。蟒蛇的实现是从来没有 保证 要做到这一点,但是他们都做更多的不仅仅是小int.
对于一件事,还有一些其他的预创造的价值,例如空 tuple
, str
, , bytes
, 和一些短串(在CPython3.6,这是256个字的拉丁文-1strings)。例如:
>>> a = ()
>>> b = ()
>>> a is b
True
而且,甚至非预创造的价值可以以完全相同。考虑这些例子:
>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True
这不是限于 int
值:
>>> g, h = 42.23e100, 42.23e100
>>> g is h
True
显然,CPython不来一个前创建的 float
值 42.23e100
.那么,什么会在这里?
该CPython编译器将合并常数值中的一些已知的不变喜欢的类型 int
, float
, str
, bytes
, 在相同的汇编单元。对于一个模块,整个模块,汇编单位,但在交互式解释,每一个声明是一个单独编制单元。由于 c
和 d
定义在单独发言,他们的价值观是不是合并。由于 e
和 f
定义在同一声明中,他们的价值是合并。
你可以看到发生了什么事情上通过拆卸码。尝试界定一个函数 e, f = 128, 128
然后叫 dis.dis
上它,你就会看到,有一个单一的常数值 (128, 128)
>>> 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
你可能会注意到,已编译器储存 128
作为一个恒定的,即使它不是实际使用的字节,这让你有一个想法怎么一点优化CPython的编译器。这意味着(non-empty)组实际上没有最终合并:
>>> k, l = (1, 2), (1, 2)
>>> k is l
False
把那个放在一个功能, dis
这,看看 co_consts
—有一个 1
和一个 2
,两个 (1, 2)
组共享相同的 1
和 2
但不完全相同,和 ((1, 2), (1, 2))
元组,有两个不同的平等元组。
还有一个优化,CPython:串的实习.不像编译器不断的折叠,这并不仅限于源代码文字:
>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True
另一方面,它是有限的 str
的类型,并串 内部存储种"ascii契约"、"契约"、或"传统准备就绪", ,而且在许多情况下,只有"ascii契约"将会得到拘留.
无论如何,该规则为什么样的价值观必须是,可能或不可能不同而异,从实施执行情况,并版本之间的相同实施,甚至可能运行之间的相同的代码相同的副本相同的实施。
它可以是值得学习的规则对于一个特定的蟒蛇的乐趣。但这不值得依赖于他们在你的代码。唯一安全的规则是:
- 不要写代码,假定两个相等但分开创不可改变的价值是相同的。
- 不要写代码,假定两个相等但分开创不可改变的价值观是不同的。
或者,换句话说,仅仅使用 is
来测试记录的单身(似的 None
)或是只有建立在一个地方,在代码(喜欢的 _sentinel = object()
成语).
这也恰好与字符串:
>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)
现在一切似乎都很好。
>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)
这是期望太高。
>>> 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)
现在这是出乎意料的。