为什么使用'=='或'is'比较字符串有时会产生不同的结果?

1311

两个字符串变量被赋予相同的值。s1 == s2 永远返回 True,但是 s1 is s2 有时会返回 False

如果我在Python解释器中执行同样的 is 比较,它会成功:

>>> s1 = 'text'
>>> s2 = 'text'
>>> s1 is s2
True

为什么会这样?


9
好的,我会尽力而为。以下是需要翻译的内容:问题链接:https://dev59.com/yXM_5IYBdhLWcg3wZSTX问题:在Python中,为什么字符串“hello” == “hello”的结果是True,但是“hello” is “hello”的结果却是False?答案:“==”用于比较值是否相等,“is”用于比较对象是否相同。当创建一个新的字符串时,解释器会在内存中的不同位置创建它,因此使用“is”进行比较时,它们并不相同。但是,对于一些共享的字符串(例如由编译器提前创建的字符串),它们在内存中只有一个实例,因此使用“is”进行比较时会返回True。因此,在大多数情况下,您应该使用“==”来比较字符串的值是否相等,而不是使用“is”来比较它们是否相同的对象。 - Nick Dandoulakis
3
当你通过例如input = raw_input("Decide (y/n): ")来读取控制台输入时,也会出现这个问题。在这种情况下,“y”作为输入并使用if input == 'y':会返回“True”,而使用if input is 'y':会返回False。 - Semjon Mössinger
4
这篇博客比任何答案都提供了更完整的解释:http://guilload.com/python-string-interning/ - Chris_Rands
1
正如@chris-rico所提到的,这里有一个很好的解释https://dev59.com/nWUp5IYBdhLWcg3wEEXd - ThorSummoner
4
可能是Python中'=='和'is'有什么区别吗?的重复问题。 - Taknok
显示剩余2条评论
15个回答

1670

is 是用于身份测试的,而 == 是用于相等性测试的。你代码中发生的情况在解释器中会被模拟成:

>>> a = 'pub'
>>> b = ''.join(['p', 'u', 'b'])
>>> a == b
True
>>> a is b
False

因此,他们不相同也就不足为奇了,对吧?

换句话说:a is b 等价于 id(a) == id(b)


19
明白了,"ahh same as eq? vs equal? in scheme" 的意思是 "啊,scheme中的'same as eq?'和'equal?'是相同的吗?"。 - jottos
57
Java中的==.equals()之间的区别。最棒的部分是Python中的==与Java中的==不相似。 - Tyler
12
只有一个None值,因此它始终具有相同的标识符ID。 - SilentGhost
24
这并没有解决原帖中的 "is -> True" 的例子。 - user2864740
8
@AlexanderSupertramp,因为Python中的字符串驻留机制。 - Chris Rico
显示剩余7条评论

660
其他答案是正确的: is 用于身份比较,而 == 用于相等性比较。由于您关心的是相等性(两个字符串应包含相同的字符),因此在这种情况下,is 运算符是错误的,您应该改用 ==

is 之所以可以交互使用,是因为(大多数)字符串文字默认情况下会被内部化。来自Wikipedia:

  

内部化的字符串加速了字符串比较,这在某些应用程序中(例如依赖具有字符串键的哈希表的编译器和动态编程语言运行时)是性能瓶颈。   没有内部化,检查两个不同的字符串是否相等涉及检查两个字符串的每个字符。由于以下几个原因,这很慢:它本质上是O(n)长的字符串;通常需要从多个存储区读取,需要时间;阅读填充处理器高速缓存,这意味着其他需求可用的缓存更少。有了内部化的字符串,最初的内部化操作后一个简单的对象标识测试就足够了。这通常实现为指针等式测试,通常只是一个机器指令,根本没有内存引用。

因此,当您的程序中有两个具有相同值的字符串文字(字面上输入到您的程序源代码中的单词,用引号括起来)时,Python编译器会自动将字符串内部化,使它们都存储在相同的内存位置。 (请注意,这不一定总会发生,并且关于何时发生此操作的规则非常复杂,因此请勿在生产代码中依赖此行为!)

由于在交互式会话中,这两个字符串实际上存储在同一内存位置,它们具有相同的身份(identity),因此is运算符按预期工作。但是,如果您通过其他方法构造一个字符串(即使该字符串包含完全相同的字符),则该字符串可能相等,但它不是同一字符串--也就是说,它具有不同的身份(identity),因为它存储在内存中的另一个位置。


8
有关字符串何时被加入到内存池中的复杂规则,有哪些地方可以阅读更多相关信息? - Noctis Skytower
110
将英语翻译成中文。仅返回翻译后的文本:+1给出详细解释。不确定为什么其他答案没有解释实际发生了什么就得到了这么多赞。 - That1Guy
6
当我读到这个问题时,这正是我所想的。被接受的答案很短,但包含了事实,而这个答案则更好地解释了事情。不错! - Sнаđошƒаӽ
5
@NoctisSkytower 搜了一下,找到了这个网站:http://guilload.com/python-string-interning/。 - xtreak
6
规则是根据想要哪种检查而选择使用==is。如果您关心字符串是否相等(即具有相同的内容),则应始终使用==。如果您关心任何两个Python名称是否引用相同的对象实例,则应使用is。如果您编写的代码处理许多不同的值而不关心它们的内容,或者如果您知道只有一个某物并且想要忽略其他伪装成该物体的对象,则可能需要is。如果您不确定,请始终选择== - Daniel Pryden
显示剩余5条评论

121

is 关键字用于测试对象的身份(identity),而== 用于值比较。

如果使用 is,只有当对象是同一个对象时结果才为true。然而,只要对象的值相同,== 的结果就为true。


65

需要注意的是,您可以使用sys.intern函数来确保获取到的是同一个字符串的引用:

>>> from sys import intern
>>> a = intern('a')
>>> a2 = intern('a')
>>> a is a2
True

正如之前的回答中指出的那样,你不应该使用is来判断字符串是否相等。但如果你有某种奇怪的要求需要使用is,这可能会有所帮助。

请注意,在Python 2中使用的intern函数曾经是内置函数,但在Python 3中已经将其移动到sys模块中。


49

is 是身份测试,而== 是相等性测试。这意味着is是一种检查两个事物是否是相同的事物或者仅仅是等价的方式。

假设你有一个简单的person对象。如果它被命名为'Jack'并且年龄为'23'岁,则它相当于另一个23岁的Jack,但不是同一个人。

class Person(object):
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def __eq__(self, other):
       return self.name == other.name and self.age == other.age

jack1 = Person('Jack', 23)
jack2 = Person('Jack', 23)

jack1 == jack2 # True
jack1 is jack2 # False

他们同龄,但不是同一实例的人。一个字符串可能等价于另一个字符串,但它们并不是同一个对象。


1
如果你改变jack1.age = 99,那不会改变jack2.age。这是因为它们是两个不同的实例,所以jack1不等于jack2。然而,如果它们的名字和年龄相同,它们可以相等jack1 == jack2。对于字符串来说,情况会更加复杂,因为在Python中字符串是不可变的,并且Python经常重用相同的实例。我喜欢这个解释,因为它使用了简单的情况(普通对象)而不是特殊情况(字符串)。 - Flimm

40

1
对于 True 和 False,这个说法是否也适用?只有一个实例时,is 会匹配吗? - HandyManDan
1
@HandyManDan 是的,在 Python 2 和 3 中它们都是单例。 - kamillitw
1
@kamillitw 但在Python 2中,您可以重新分配False和True。 - Martijn Pieters

34

如果您不确定自己在做什么,请使用“==”。

如果您对此有更多了解,可以对已知对象如“None”使用“is”。

否则,您最终会想知道为什么事情不起作用以及为什么会发生这种情况:

>>> a = 1
>>> b = 1
>>> b is a
True
>>> a = 6000
>>> b = 6000
>>> b is a
False

我甚至不确定在不同的Python版本/实现之间是否有一些保证会保持不变。


1
有趣的例子展示了重新分配整数如何触发此条件。为什么会失败?是由于内部化还是其他原因? - Paul
看起来 is 返回 false 的原因可能是解释器的实现:https://dev59.com/ZHVC5IYBdhLWcg3w-WOs - Paul
1
阅读:https://dev59.com/DHVC5IYBdhLWcg3wZwTj 和 https://dev59.com/WGgu5IYBdhLWcg3wOUu- - Archit Jain
@ArchitJain 是的,那些链接解释得很清楚。当你阅读它们时,你会知道哪些数字可以使用“is”。我只是希望他们能解释为什么这仍然不是一个好主意 :) 你知道这一点并不意味着假设每个人都知道(或者内部化的数字范围永远不会改变)是一个好主意。 - Mattias Nilsson

26

根据我对Python的有限经验,is用于比较两个对象是否为同一对象,而不是两个具有相同值的不同对象。 ==用于确定值是否相同。

这里有一个很好的例子:

>>> s1 = u'public'
>>> s2 = 'public'
>>> s1 is s2
False
>>> s1 == s2
True

s1 是一个 Unicode 字符串,而 s2 是一个普通字符串。它们虽然不是相同类型,但却有相同的值。


1
这个结果是由于不同的原因造成的:比较一个Unicode字符串(<type 'unicode'>)和一个非Unicode字符串(<type 'str'>)。这是Python 2特定的行为。在Python 3中,s1s2都是str类型,并且is==都返回True - 0 _

22
我认为这与以下事实有关:当“is”比较评估为false时,使用了两个不同的对象。如果评估结果为true,则意味着内部使用的是完全相同的对象,而不是创建新对象,可能是因为您在2秒左右的时间内创建了它们,并且由于之间没有大的时间间隔,所以它被优化并使用相同的对象。
这就是为什么您应该使用等号运算符“==”,而不是“is”,来比较字符串对象的值。
>>> s = 'one'
>>> s2 = 'two'
>>> s is s2
False
>>> s2 = s2.replace('two', 'one')
>>> s2
'one'
>>> s2 is s
False
>>> 
在这个例子中,我创建了一个名为s2的字符串对象,并将其赋值为'one',但它不是与s相同的对象,因为解释器并没有使用与我最初未将其分配为'one'时所用的相同对象,如果我这样做了,它们就是相同的对象。

5
在这种情况下,以 .replace() 作为示例可能并不是最好的,因为它的语义可能会让人感到困惑。无论如何,s2 = s2.replace() 都将始终创建一个新的字符串对象,将新的字符串对象分配给 s2,然后处理 s2 原来指向的字符串对象。因此,即使你执行 s = s.replace('one', 'one'),你仍将得到一个新的字符串对象。 - Daniel Pryden

18

== 运算符测试值的等价性。 is 运算符测试对象的身份,Python 测试这两个是否真的是同一个对象(即在内存中存在于同一地址)。

>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True
在这个例子中,Python只创建了一个字符串对象,同时ab都指向它。原因是Python内部缓存并重用一些字符串作为优化。实际上内存中只有一个字符串'banana',被a和b共享。如果要触发正常行为,需要使用更长的字符串:
>>> a = 'a longer banana'
>>> b = 'a longer banana'
>>> a == b, a is b
(True, False)

当你创建两个列表时,你会得到两个对象:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False

在这种情况下,我们会说这两个列表是等价的,因为它们具有相同的元素,但不是相同的,因为它们不是同一对象。如果两个对象是相同的,那么它们也是等价的,但如果它们是等价的,则不一定相同。

如果a引用一个对象,并且你将b = a分配给它,那么两个变量都引用同一个对象:

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True

参考资料: Think Python 2e作者: Allen B. Downey


这是唯一一个真正解释所见行为的答案,特别是它有时会以一种方式工作,有时会以另一种方式工作,这取决于字符串长度。顺便说一下,对两个字符串执行lower()操作也可能会将结果从True变为False。 - rossdavidh

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