自从我开始学习 python 已经有几天了,那时我偶然发现了==
and is
。来自java背景,我假设==
通过对象ID和is
值进行比较,但是做
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a == b
True
似乎is
相当于 java 的==
,python==
相当于 java 的equals()
. 这是思考is
和之间区别的正确方法==
吗?还是有警告?
自从我开始学习 python 已经有几天了,那时我偶然发现了==
and is
。来自java背景,我假设==
通过对象ID和is
值进行比较,但是做
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a == b
True
似乎is
相当于 java 的==
,python==
相当于 java 的equals()
. 这是思考is
和之间区别的正确方法==
吗?还是有警告?
is
检查两个操作数是否是同一个对象。==
调用__eq__()
左操作数,传递右操作数。通常,此方法实现相等比较,但可以编写一个将其用于其他目的的类(但绝不应该)。
请注意,在某些实现中,对于某些对象(字符串文字、介于 -1is
和==
256 之间的整数)会给出相同的结果,但这并不意味着在这些情况下应该将运算符视为可替换的。
跟进@CRUSADER 的回答:
==
使用eq方法检查对象的相等性。
is
检查对象的实际内存位置。如果它们是相同的内存位置,则测试为True
如上所述,前 2**8 个整数存储在内存位置以提高速度,因此要查看发生了什么,请使用 256 以上的其他对象或整数。例如:
In [8]: a = 1001
In [9]: b = a # this sets a pointer to a for the variable b
In [10]: a == b
Out[10]: True # of course they are equal
In [11]: a is b
Out[11]: True # and they point to the same memory location
In [12]: id(a)
Out[12]: 14125728
In [13]: id(b)
Out[13]: 14125728
In [14]: b = 1001 #this instantiates a new object in memory
In [15]: a == b
Out[15]: True
In [16]: a is b
Out[16]: False #now the memory locations are different
In [17]: id(a)
Out[17]: 14125728
In [18]: id(b)
Out[18]: 14125824
这是看似同义的概念可能会使新程序员感到困惑的情况之一,例如我第一次写这个答案时。您与基于 Java 的假设很接近,但倒退了。这些运算符之间的区别归结为对象等效与对象身份的问题,但与您假设的相反,==
按值is
比较并按对象 id 比较。来自 cpython 的内置文档(通过help("is")
在我的解释器提示下键入获得,但也可在此处在线获得):
身份比较 =====================
运算符“是”和“不是”测试对象身份:当且仅当x和y是同一个对象时,“x 是 y”为真。对象身份是使用“id()”函数确定的。“x is not y”产生逆真值。
为了让经验不足的程序员(或任何需要复习的人)稍微了解一下,每个概念的粗略定义如下:
对象等效性:如果两个引用具有相同的有效值,则它们是等效的。
对象标识:如果两个引用指向同一个确切的对象,例如相同的内存位置,则它们是相同的
对象等价发生在您可能预期的大多数情况下,例如比较2 == 2
或[0, None, "Hello world!"] == [0, None, "Hello world!"]
. 对于内置类型,这通常是根据对象的值来确定的,但是用户定义的类型可以通过定义__eq__
方法来定义自己的行为(尽管仍然建议以反映对象的完整值的方式这样做)物体)。对象同一性是可以导致对等的东西,但总的来说,完全是一个单独的问题。对象标识严格取决于 2 个对象(或者更确切地说,2 个引用)是否引用内存中完全相同的对象,由id()
. 关于相同引用的一些有用说明:因为它们引用内存中的相同实体,所以它们总是(至少在 cpython 中)具有相同的值,并且除非__eq__
被非常规地定义,因此将是等价的。如果您尝试通过就地操作(例如list.append()
or )更改其中一个引用,这甚至成立my_object[0]=6
,并且应注意测试身份并制作应该分开的对象的副本(这是 的主要目的之一is
:检测和处理别名)。例如:
>>> first_object = [1, 2, 3]
>>> aliased_object = first_object
>>> first_object is aliased_object
True
>>> aliased_object[0]= "this affects first_object"
>>> first_object
['this affects first_object', 2, 3]
>>> copied_object= first_object.copy() #there are other ways to do this, such as slice notation or the copy module, but this is the most simple and direct
>>> first_object is copied_object
False
>>> copied_object[2] = "this DOES NOT affect first_object"
>>> first_object
['this affects first_object', 2, 3]
>>> copied_object
['this affects first_object', 2, "this DOES NOT affect first_object"]
有很多情况会导致 2 个引用被别名,但是在赋值运算符之外(它总是创建对分配对象的引用,如上所述),其中许多依赖于确切的实现,例如,不是 Python 的每个实现将在相同情况下实习字符串或抢先缓存(我不确定在这种情况下正确的术语是什么)相同的整数范围。例如,我安装的 cpython 似乎在启动时缓存了 -8,而本文似乎暗示这超出了正常范围。因此,即使is
看起来在您的开发环境中工作,最好站在同一边,完全避免不一致的行为,并使用==
. is
应该保留用于您实际想要比较身份的情况。