为什么下面的表现出乎意料在蟒蛇?

>>> 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)

那是什么 NSMALLNEGINTSNSMALLPOSINTS?如果你猜到了宏你什么也得不到,因为这不是一个很难的问题.. 无论如何,他们在这里:

#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不来一个前创建的 float42.23e100.那么,什么会在这里?

该CPython编译器将合并常数值中的一些已知的不变喜欢的类型 int, float, str, bytes, 在相同的汇编单元。对于一个模块,整个模块,汇编单位,但在交互式解释,每一个声明是一个单独编制单元。由于 cd 定义在单独发言,他们的价值观是不是合并。由于 ef 定义在同一声明中,他们的价值是合并。


你可以看到发生了什么事情上通过拆卸码。尝试界定一个函数 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) 组共享相同的 12 但不完全相同,和 ((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)

现在这是出乎意料的。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top