理解“is”运算符

124

is运算符不匹配变量的值,而是匹配实例本身。

这句话真正的意思是什么?

我声明了两个变量xy,并将相同的值分配给它们,但是当我使用is运算符时,它返回false。

我需要澄清一下。这是我的代码。

x = [1, 2, 3]
y = [1, 2, 3]

print(x is y)  # It prints false!

相关问题 https://dev59.com/7loT5IYBdhLWcg3wxxsl#38189759 - Mazdak
11个回答

204

你误解了 is 运算符的作用,它检测的是两个变量是否指向同一个对象,而不是它们的值是否相等。

根据 is 运算符的文档

isis not 这两个运算符测试对象标识。当且仅当 xy 是同一个对象时才返回 True,否则返回 False。

请使用 == 运算符代替:

print(x == y)

这将打印出Truexy 是两个分离的列表:

x[0] = 4
print(y)  # prints [1, 2, 3]
print(x == y)   # prints False
如果您使用id()函数,您将看到xy具有不同的标识符。
>>> id(x)
4401064560
>>> id(y)
4401098192

但是如果您将y赋值给x,则两者都指向同一个对象:

>>> x = y
>>> id(x)
4401064560
>>> id(y)
4401064560
>>> x is y
True

并且is显示两个变量指向的是同一个对象,它返回True

请记住,在Python中,名称只是用来引用值的标签;你可以有多个名称指向相同的对象。is告诉你两个名称是否指向同一个对象。==告诉你两个名称是否引用具有相同值的对象。


17
因此,“A是B”与“id(A)==id(B)”相同。 - geometrian
3
如果您没有将id(A)存储在变量中并且希望以后variable == id(B)仍然起作用,那么这就是相同测试的代理;如果A在此期间被删除,则B可能已获得相同的内存位置。请注意不要改变原来的意思。 - Martijn Pieters
有意义,而且也是正确的事情;variable存储了过去存在的某个东西的属性。运行时无法检测后续使用是否错误。标准的关键部分是“[id()]保证在此对象的生命周期内是唯一且恒定的。具有非重叠生命周期的两个对象可能具有相同的id()值。” - geometrian

70

另一个重复的问题在询问为什么两个相等的字符串通常不是相同的,这里并没有得到答案:

>>> x = 'a' 
>>> x += 'bc'
>>> y = 'abc'
>>> x == y
True
>>> x is y
False

所以,为什么它们不是相同的字符串?特别是考虑到这一点:

>>> z = 'abc'
>>> w = 'abc'
>>> z is w
True

让我们暂时搁置第二部分。第一部分怎么可能是真的呢?

解释器必须有一个“interning table”,即将字符串值映射到字符串对象的表,这样每次您尝试使用内容为'abc'的新字符串时,都会得到相同的对象。 Wikipedia 对内部化工作原理进行了更详细的讨论。

Python确实有一个字符串内部化表;您可以使用sys.intern方法手动内部化字符串。

实际上,Python允许自动内部化任何不可变类型,但不要求这样做。不同的实现将内部化不同的值。

CPython(如果您不知道使用哪个实现,则使用此实现)自动内部化小整数和一些特殊单例,如False,但不是字符串(或大整数、小元组或其他任何内容)。您可以很容易地看到这一点:

>>> a = 0
>>> a += 1
>>> b = 1
>>> a is b
True
>>> a = False
>>> a = not a
>>> b = True
a is b
True
>>> a = 1000
>>> a += 1
>>> b = 1001
>>> a is b
False

好的,但为什么zw是相同的?

这不是解释器自动进行的内部化,而是编译器折叠值。

如果同一编译时字符串在同一模块中出现两次(这个定义很难确定 - 它不是与字符串文字相同的东西,因为r'abc''abc''a''b''c'是所有不同的文字,但是易于直观理解),编译器将仅创建一个字符串实例,并引用两个引用。

实际上,编译器甚至可以进一步:优化器可以将'ab'+'c'转换为'abc',在这种情况下,它可以与同一模块中的'abc'常量一起折叠。

同样,这是Python允许但不要求执行的操作。但在这种情况下,CPython始终折叠小字符串(以及例如小元组)。 (尽管交互式解释器的逐条语句编译器不会运行与模块一次性编译器相同的优化,因此您不会在交互式环境中看到完全相同的结果。)


那么,作为一名程序员,你应该怎么做呢?

嗯...什么都不用做。你几乎从来没有理由关心两个不可变的值是否相同。如果你想知道何时可以使用a is b而不是a == b,那你问错了问题。除了以下两种情况外,始终使用a == b

  • 为了更易读地比较单例值,如x is None
  • 对于可变值,当你需要知道修改x是否会影响y时。

谢谢您提供的详细解释。有人知道吗:如果wz由于编译器折叠值而相同,为什么在REPL中使用id()检查引用时也能正常工作呢?在Python 3.7上使用REPL - Chi-chi

8

is只有在它们实际上是同一个对象时才返回true。如果它们相同,对一个对象的更改也会显示在另一个对象中。以下是差异的示例。

>>> x = [1, 2, 3]
>>> y = [1, 2, 3]
>>> print x is y
False
>>> z = y
>>> print y is z
True
>>> print x is z
False
>>> y[0] = 5
>>> print z
[5, 2, 3]

8
受到重复问题的启发,以下类比或许有帮助:
# - Darling, I want some pudding!
# - There is some in the fridge.

pudding_to_eat = fridge_pudding
pudding_to_eat is fridge_pudding
# => True

# - Honey, what's with all the dirty dishes?
# - I wanted to eat pudding so I made some. Sorry about the mess, Darling.
# - But there was already some in the fridge.

pudding_to_eat = make_pudding(ingredients)
pudding_to_eat is fridge_pudding
# => False

4
可能只是个人口味问题(没有讽刺的意思),但我发现这个比喻更加令人困惑,让我想吃布丁,可我冰箱里并没有 :(我认为马克·兰索姆的回答虽然更加无聊,但可能更具有指导性。 - Tom Close
1
@TomClose:这个问题有很多好的答案,足以容纳幽默感。另外,我也想要布丁。 - Amadan

6

isis not 是 Python 中的两个身份运算符。 is 运算符不比较变量的值,而是比较变量的身份。考虑以下示例:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> hex(id(a))
'0x1079b1440'
>>> hex(id(b))
'0x107960878'
>>> a is b
False
>>> a == b
True
>>>

上面的例子向您展示了,即使ab的值相同,它们的标识(在Cpython中也可以是内存地址)也不同。这就是为什么当您说a is b时,由于两个操作数的标识不匹配,它返回false。但是,当您说a == b时,它返回true,因为==操作只验证是否将相同的值分配给两个操作数。
有趣的例子(额外加分):
>>> del a
>>> del b
>>> a = 132
>>> b = 132
>>> hex(id(a))
'0x7faa2b609738'
>>> hex(id(b))
'0x7faa2b609738'
>>> a is b
True
>>> a == b
True
>>>

在上面的例子中,尽管变量a和b是两个不同的变量,但a is b返回True。这是因为a的类型是int,它是一个不可变的对象。因此,Python(我猜是为了节省内存)在创建具有相同值的b时分配了相同的对象。因此,在这种情况下,变量的标识符匹配,并且a is b结果为True。
这适用于所有不可变的对象:
>>> del a
>>> del b
>>> a = "asd"
>>> b = "asd"
>>> hex(id(a))
'0x1079b05a8'
>>> hex(id(b))
'0x1079b05a8'
>>> a is b
True
>>> a == b
True
>>>

希望这能帮到你。

但是尝试一下 a=123456789 b=123456789 - user2183078
1
在Python中,小于-5或大于256的所有数字都将为False。Python会缓存范围在[-5,256]之间的数字。 - smart
并非所有不可变对象都会被共享,这是 Python 运行时针对某些对象应用的一种优化,而对于其他对象则不是。分享小整数的过程有着详细的文档记录,但我认为这并不适用于字符串驻留。 - Mark Ransom

4

不,它并不是这样的。在大多数情况下,它可能表现得类似,但并不总是正确的。请参见此处 - 页面底部,第6个项目:
(...),您可能会注意到某些使用is运算符的看似不寻常的行为,例如涉及实例方法或常量之间的比较
还有一个最小工作示例:class A(object): def foo(self): pass a = A() print a.foo is a.foo print id(a.foo) == id(a.foo)
- Tomasz Kurgan

3

您可以在这里检查小整数。 257以上的数字不是小整数,因此会被计算为不同的对象。

在这种情况下最好使用 ==

更多信息请参见:http://docs.python.org/2/c-api/int.html


2

X指向一个数组,Y指向另一个不同的数组。这些数组是相同的,但是is运算符将查看那些指针,它们并不相同。


5
Python 没有指针。你需要严密化术语。 - David Heffernan
3
的确,它和Java还有许多其他语言一样,在内部进行操作。事实上,“is”运算符的功能表明了这一点。 - Neko
5
实现细节并不重要。文档中使用了"对象标识"这个术语,你也应该这样做。"is"和"is not"运算符用于测试对象标识:当且仅当x和y是同一对象时,"x is y"为真。"x is not y"则返回相反的真值。 - David Heffernan
1
@Neko:CPython 内部使用指针。但是显然,Jython(用 Java 实现)和 PyPy(用 Python 的子集实现)不使用指针。在 PyPy 中,除非你要求,否则某些对象甚至没有 id - abarnert

1
一个关于水果的简单示例。
fruitlist = [" apple ", " banana ", " cherry ", " durian "]
newfruitlist = fruitlist
verynewfruitlist = fruitlist [:]
print ( fruitlist is newfruitlist )
print ( fruitlist is verynewfruitlist )
print ( newfruitlist is verynewfruitlist )

输出:

True
False
False

如果你尝试

fruitlist = [" apple ", " banana ", " cherry ", " durian "]
newfruitlist = fruitlist
verynewfruitlist = fruitlist [:]
print ( fruitlist == newfruitlist )
print ( fruitlist == verynewfruitlist )
print ( newfruitlist == verynewfruitlist )

输出结果不同:

True
True
True

那是因为 == 操作符仅比较变量的内容。要比较两个变量的身份,请使用 is 操作符。
要打印标识号:
print ( id( variable ) )

1

它比较对象标识,也就是变量是否引用内存中的同一对象。这类似于Java或C中的==(当比较指针时)。


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