Python中的"!="操作符和"is not"有什么区别?

336

这个问题的评论中,我看到了一个建议使用的声明

result is not None

VS

result != None

有什么区别?为什么可能推荐使用其中之一?


5
复制品:https://dev59.com/dHI_5IYBdhLWcg3wJfkM - SilentGhost
3
嗯。虽然这两个问题的答案是相同的概念,但我认为这里的点赞和详细回答对身份和相等测试的概念有独立的贡献。 - viksit
5个回答

371

== 是一个相等性测试。它检查左右两侧是否是相等的对象(根据它们的 __eq____cmp__ 方法)。

is 是一个身份测试。它检查左右两侧是否是同一个对象。不进行方法调用,对象不能影响 is 操作。

你可以使用 is(和 is not)来检查单例对象,例如 None ,这样你就不需要关心那些想要假装成 None 的对象,或者你想保护对象不因为与 None 进行比较而出现问题。


5
谢谢你的提问。您能详细解释一下对象与 None 比较时可能导致对象破裂的情况吗? - viksit
6
None几乎没有任何方法和属性。如果您的__eq__测试期望一个方法或属性,它可能会出错。例如,如果other恰好是None,那么def __eq__(self, other): return self.size == other.size就会出错。 - S.Lott
59
我理解这个内容的方法是:Python 中的 is 就像 Java 的 ==,Python 的 == 就像 Java 的 .equals()。当然,这只对了解 Java 的人有用。 - Tyler
4
在 PHP 或 JavaScript 中,我们会说 is 就像 ===(非常相等),反之 is not 就像 !==(不完全相等)。 - Orwellophile
4
“is not”是一个单一的运算符,它与“is”的结果相反,类似于“not foo is bar”。 - Asad Moosvi
显示剩余2条评论

180

首先,让我解释一些术语。如果你只想得到问题的答案,请向下滚动到“回答您的问题”。

定义

对象标识:当你创建一个对象时,你可以将它赋值给一个变量。然后你也可以将它赋值给另一个变量。再接着另一个。

>>> button = Button()
>>> cancel = button
>>> close = button
>>> dismiss = button
>>> print(cancel is close)
True
在这种情况下,cancelclosedismiss都指向内存中的同一对象。你只创建了一个Button对象,三个变量都指向这个对象。我们说cancelclosedismiss都引用了相同的对象;也就是说,它们引用同一个对象。

对象相等性:当你比较两个对象时,通常不关心它是否引用内存中的完全相同对象。通过对象相等性,你可以定义自己的规则来比较两个对象。当你写if a == b:时,实际上是在说if a.__eq__(b):。这让你可以在a上定义一个__eq__方法,以便使用自己的比较逻辑。

相等比较的基本原理

基本原理:两个对象具有相同的数据,但不是同一个对象。(它们在内存中不是同一个对象。) 例子:字符串

>>> greeting = "It's a beautiful day in the neighbourhood."
>>> a = unicode(greeting)
>>> b = unicode(greeting)
>>> a is b
False
>>> a == b
True

注意:在这里我使用unicode字符串,因为Python足够聪明,可以重用常规字符串而不会在内存中创建新的字符串。

这里有两个unicode字符串ab,它们具有完全相同的内容,但它们在内存中不是同一个对象。然而,当我们进行比较时,我们希望它们被视为相等。实际上是unicode对象已经实现了__eq__方法。

class unicode(object):
    # ...

    def __eq__(self, other):
        if len(self) != len(other):
            return False

        for i, j in zip(self, other):
            if i != j:
                return False

        return True

注意:unicode 上的 __eq__ 可以比这个实现更高效。

原因:如果一些关键数据相同,两个对象具有不同的数据,但被视为相同的对象。示例:大多数类型的模型数据。

>>> import datetime
>>> a = Monitor()
>>> a.make = "Dell"
>>> a.model = "E770s"
>>> a.owner = "Bob Jones"
>>> a.warranty_expiration = datetime.date(2030, 12, 31)
>>> b = Monitor()
>>> b.make = "Dell"
>>> b.model = "E770s"
>>> b.owner = "Sam Johnson"
>>> b.warranty_expiration = datetime.date(2005, 8, 22)
>>> a is b
False
>>> a == b
True

我这里有两个戴尔显示器,ab。它们的型号和制造商都相同,但数据不同且在内存中也不是同一个对象。然而,当我们比较它们时,希望它们相等。这里使用了 Monitor 对象实现的 __eq__ 方法。

class Monitor(object):
    # ...

    def __eq__(self, other):
        return self.make == other.make and self.model == other.model

回答您的问题

None 比较时,总是使用 is not。在 Python 中,None 是一个单例 - 内存中只有一个实例。

通过比较 标识,可以非常快地执行此操作。Python 检查您引用的对象是否具有与全局 None 对象相同的内存地址 - 这是两个数字之间非常快速的比较。

通过比较 相等性,Python 必须查找您的对象是否具有 __eq__ 方法。如果没有,则检查每个超类是否有 __eq__ 方法。如果找到了,则调用它。如果 __eq__ 方法很慢,并且在注意到另一个对象为 None 时不立即返回,则情况尤其糟糕。

没有实现 __eq__ 吗?那么 Python 可能会在 object 上找到 __eq__ 方法并使用它 - 它只检查对象标识。

在 Python 中比较大多数其他东西时,您将使用 !=


50

请考虑以下内容:

class Bad(object):
    def __eq__(self, other):
        return True

c = Bad()
c is None # False, equivalent to id(c) == id(None)
c == None # True, equivalent to c.__eq__(None)

24

None 是一个单例对象,因此身份比较始终有效,而一个对象可以通过 .__eq__() 模拟等式比较。


啊,有趣!在什么情况下,人们可能想要伪造等式比较?我猜这在某种程度上涉及到安全问题。 - viksit
1
这不是关于伪造平等,而是关于实现平等。有很多原因想要定义一个对象如何与另一个对象进行比较。 - Thomas Wouters
1
我认为这更多是混淆的含义,而不是安全含义。 - Greg Hewgill
2
我还没有遇到过伪造与None相等的原因,但是针对其他类型实现相等性可能会导致关于None的不正确行为作为副作用发生。这并不是安全方面的问题,而更多地是正确性方面的问题。 - Ignacio Vazquez-Abrams

11
一些对象是单例,因此使用is与它们等同于使用==。大多数对象不是这样的。

4
大多数情况下这些只是偶然/实现细节起作用。 ()1并非天生单例。 - Mike Graham
1
在CPython实现中,小整数(-NSMALLNEGINTS <= n <= NSMALLPOSINTS)和空元组单例。事实上,这并没有被记录或保证,但它不太可能改变。 - ephemient
3
实现方式没问题,但缺乏意义、用处或教育价值。 - Mike Graham
1
特别地,CPython不是唯一的Python实现。依赖于在Python实现之间可能会有所不同的行为似乎通常对我来说都是一个糟糕的想法™。 - me_and

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接