为什么下面的代码在Python中会出现意外的行为?

>>> 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,似乎Python 2.3.3在99到100之间显示了上述行为。

基于以上,我可以假设Python是内部实现的,这样“小”整数与大整数以不同的方式存储,并且is操作符可以区分两者。为什么会有漏洞的抽象?有什么更好的方法来比较两个任意物体来判断它们是否相同当我事先不知道它们是否是数字时?


当前回答

Python 3.8新增功能:Python行为的变化:

编译器现在在标识检查(is和 Is not)用于某些类型的字面量(例如字符串,int)。 在CPython中,这些通常可以意外地工作,但不能保证 该警告建议用户使用相等性测试(== 和!=)代替。

其他回答

对于不可变值对象,比如int、字符串或datetimes,对象标识并不是特别有用。最好还是考虑一下平等。标识本质上是值对象的实现细节——因为它们是不可变的,所以对同一个对象的多次引用和对多个对象的多次引用之间没有有效的区别。

我认为你的假设是正确的。实验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被视为字面量,以上任何数字都被区别对待!

它也发生在字符串上:

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

这是意料之外的。

看看这个:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

以下是我在“普通整数对象”的文档中找到的:

当前的实现为-5到256之间的所有整数保留了一个整数对象数组。当你在这个范围内创建一个int型时,你实际上只是得到了一个对现有对象的引用。

Python的“is”运算符对整数表现异常?

总之,让我强调一下:不要使用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和is not测试对象的同一性:x是y是真 当且仅当x和y是同一个对象。X不是y会得到 逆真值。

所以下面两个是等价的。

>>> a is b
>>> id(a) == id(b)

从文档中可以看到:

id 返回一个对象的“标识”。这是一个整数(或长 Integer),保证该对象的唯一性和常量 在它的生命周期内。两个生命周期不重叠的对象可能 具有相同的id()值。

请注意,在CPython (Python的参考实现)中,对象的id是内存中的位置,这是一个实现细节。Python的其他实现(如Jython或IronPython)很容易有不同的id实现。

那么用例是什么呢?PEP8描述:

与像None这样的单例对象的比较应该总是使用is或 不是,不是相等运算符。

这个问题

你问并陈述以下问题(带代码):

为什么下面的代码在Python中会出现意外的行为? >>> a = 256 >>> b = 256 >>> a是b #这是一个预期的结果

这不是一个预期的结果。为什么要这样做?它只意味着a和b引用的值为256的整数是同一个整数实例。整数在Python中是不可变的,因此它们不能改变。这应该不会对任何代码产生影响。这是不应该被期待的。它只是一个实现细节。

但是,也许我们应该感到高兴的是,每次我们声明值等于256时,内存中不会有一个新的单独实例。

>>> a = 257 >>> b = 257 >>> a是b 这里发生了什么?为什么这是假的?

看起来我们现在在内存中有两个不同的整数实例,它们的值都是257。由于整数是不可变的,因此这会浪费内存。希望我们没有浪费太多时间。我们可能不是。但是这种行为并不能保证。

>>> 257等于257 True #但文字数字比较正确

好吧,这看起来像是你的特定Python实现试图变得聪明,除非必要,否则不会在内存中创建冗余值的整数。您似乎表明您正在使用Python的参考实现,即CPython。对CPython很好。

如果CPython能在全局范围内做到这一点,如果它能做到这一点(因为查找会有成本),那就更好了,也许另一个实现可以做到。

但至于对代码的影响,您不应该关心一个整数是否是一个整数的特定实例。您应该只关心该实例的值是什么,并且您可以使用普通的比较操作符,即==。

它做什么

是检查两个对象的id是否相同。在CPython中,id是内存中的位置,但它可以是其他实现中的其他唯一标识数字。用代码重申:

>>> a is b

>>> id(a) == id(b)

为什么我们要用is呢?

This can be a very fast check relative to say, checking if two very long strings are equal in value. But since it applies to the uniqueness of the object, we thus have limited use-cases for it. In fact, we mostly want to use it to check for None, which is a singleton (a sole instance existing in one place in memory). We might create other singletons if there is potential to conflate them, which we might check with is, but these are relatively rare. Here's an example (will work in Python 2 and 3) e.g.

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

我们可以看到,通过is和哨兵,我们能够区分什么时候bar被无参数调用,什么时候它被无参数调用。这些是is的主要用例——不要用它来测试整数、字符串、元组或其他类似的东西是否相等。