Python 中 is 和 == 的区别:一个问两个名字是否指向同一个对象,另一个问值是否相等。另外讲讲为什么小整数看起来不守这条规则。
== 问的是两个对象的值是否相同。is 问的是它们是不是内存里的同一个对象。
两者结果一致的情况很多,用错了通常也能跑,正因如此,出问题时才是很难查的 bug。
区别
内容相同的两个列表相等,但不是同一个对象。
# == compares values, is compares identity
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b, a is b)
c = a
print(a == c, a is c)
输出:
True False
True True
把一个赋值给另一个,两个判断就都为真,因为只有一个列表。
判断 None 用 is
None 是单例,全局只有一个,所以该问的是标识。
# for None, always use is
value = None
print(value is None, value == None)
输出:
True True
== None 通常能用,但仍然是错的,原因见下一节。
相等可以被重新定义
类可以让 == 表示任何意思。is 无法重写。
# == can be redefined, is cannot
class Always:
def __eq__(self, other):
return True
x = Always()
print(x == 'anything', x == 42)
print(x is Always())
输出:
True True
False
如果拿 == None 来比较这个对象,判断会通过,你处理 None 的代码就会在一个真实对象上运行。
为什么小整数看起来是同一个对象
CPython 会缓存小整数,同一个代码对象里的常量也是共享的。于是 is 会在你以为是 False 的时候返回 True。
# small integers are cached by CPython
m, n = 256, 256
print('256:', m is n)
p = 1000
q = 1000
print('1000 literals:', p is q) # same code object, so the constant is shared
r = int('1000')
print('1000 at runtime:', p is r, p == r)
输出:
256: True
1000 literals: True
1000 at runtime: False True
最后一行才说了实话。以同样方式写出的两个 1000 在这里共享一个对象,运行时构造出来的 1000 却不共享。这些都不是语言保证的行为,所以千万别写依赖它的比较。
字符串也一样
字符串字面量会被驻留(intern),运行时构造的字符串不会。
# the same happens with short strings
s1 = 'hello'
s2 = 'hello'
print('literal:', s1 is s2)
s3 = ''.join(['hel', 'lo'])
print('built at runtime:', s1 is s3, s1 == s3)
输出:
literal: True
built at runtime: False True
所以 if name is "admin" 在测试时能通过,到了生产环境就失败,因为那时名字是从请求里传进来的。
标识能告诉你对象有没有被修改
这是 is 有用的一面。它能看出一个操作是创建了新对象,还是改动了原来那个。
# identity is what actually changed
first = [1, 2]
second = first
first = first + [3] # rebinds, new object
print(first, second, first is second)
third = [1, 2]
fourth = third
third += [3] # mutates in place
print(third, fourth, third is fourth)
输出:
[1, 2, 3] [1, 2] False
[1, 2, 3] [1, 2, 3] True
+ 构建了新列表,并把名字重新绑定过去,所以第二个名字看到的还是旧值。+= 原地修改了列表,所以两个名字都能看到变化。屏幕上的结果一样,但对其他持有引用的代码来说,行为完全不同。
要点
==比较值,is比较标识。is只用于None、True、False和哨兵对象。- 小整数和字面量字符串的缓存是 CPython 的实现细节,别在上面构建逻辑。
- 想检查对象是被复制了还是被修改了,就用
is。
正因如此,代码检查工具会标记拿 is 和字面量比较的写法。如果你用的工具没有这项检查,就把它打开。