is和==的区别

1、is 比较的是地址,注意小整数池和字符串,一般重复创建的时候会指向同一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
t1 = 123
t2 = 123
print(t1 is t2) # True
print(id(t1), id(t2)) # 1789477024 1789477024

print("*********************************")

l1 = [1,2,3]
l2 = [1,2,3] # False,因为会重新声明一个对象
# l2 = l1 # True,因为是赋值符号,就是指针指向
print(l1 is l2) # False
print(id(l1), id(l2)) # 56509864 56511024

因为在python中存在intern机制,适用于小整数池和字符串,也就是说创建对象的时候会先在小整数池中查找,如果存在就返回,否则就会新建对象。记住仅仅适用于小整数池和字符串,对于list是不使用的

2、== 比较的是值,其实==重载了对象的__eq__方法,而这个方法比较的是对象的值。

1
2
3
4
5
6
7
8
t1 = 123
t2 = 123
print(t1 == t2) # True

print("*********************************")
l1 = [1,2,3]
l2 = [1,2,3]
print(l1 == l2) # True

因为我们在使用==对两个对象进行值判断的时候,其实就是调用了__eq__这个魔法函数,查看一下str中的__eq__魔法函数:

1
2
3
def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass

再来看一下list对象中的__eq__魔法函数:

1
2
3
def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass

你会发现两者是一模一样,因此==就是对值进行判断,只要值相等就是True。

我们再来看一个例子,在判断类的类别时,是使用is,而不是==,原因在于type(obj)返回的是某个类,而类是一个全局唯一的变量,这种做法可以避免很多未知的错误:

1
2
3
4
5
6
7
class Person:
pass

person = Person()

if type(person) is Person: # 记住这里最好使用is,不要使用==
print("yes")

python的intern机制

由于变量的存储机制,python增加了字符串的intern机制,也就是说值同样的字符串对象(整数也实用)仅仅会保存一份,而且是共用的,这也决定了字符串必须是不可变对象,其实仔细想一想,就和数值类型一样,同样的数值仅仅要保存一份即可了,不是必须用不同对象来区分。

我们来看几个例子加深我们的认识,开启Python的IDE,必须使用Python自带的交互式环境,不能使用Pycharm:

1
2
3
4
5
6
7
>>> a = 'test'
>>> b = 'tes' + 't'
>>> print(id(a), id(b))
>>> print(a is b)
# 运行结果:
# 51379584 51379584
# True

从结果中可以看出它们实际上是同一个对象,所以用is判断就是正确。


intern机制优点是:在创建新的字符串对象时,会先在缓存池里面找是否有已经存在的值相同的对象(标识符,即只包含数字、字母、下划线的字符),如果有则直接拿过来用(引用),避免频繁的创建和销毁内存,提升效率。

缺点是在拼接字符串时或者在改动字符串时会极大的影响性能。原因是字符串在Python当中是不可变对象,所以对字符串的改动不是inplace(原地)操作,需要新开辟内存地址,即新建对象。这也是为什么拼接字符串的时候不建议用‘+’而是用join()。join()是先计算出全部字符串的长度,然后再一一拷贝,仅仅创建一次对象。


再来看这个例子:

1
2
3
4
5
6
7
#这里只是演示了包含空格的情况,实际上除了字母,数字,下划线以外的都是这个情况
>>> j = "hello world"
>>> k = "hello world"
>>> print(id(j), id(k))
# 93423920 95800760
>>> print(j is k)
# False

你可能会好奇怎么是False,因为这里的字符串里面含有空格,导致不会触发intern机制。 也就是说字符串中没有空格则会默认开启intern机制,有则就不会开启了。

你现在可能会好奇Python为什么会这么做呢?因为Python的内置函数intern()能显式的对任意字符串进行intern,就说明并不是实现难度的问题,解决这个问题最好是查看Python的源码,可以找到答案,在源代码StringObject.h中的注释能够找到:

1
/* … … This is generally restricted tostrings that “looklike” Python identifiers, although the intern() builtincan be used to force interning of any string … … */

也就是说intern机制仅仅对那些看起来像是Python标识符的字符串对象才会触发。

我们再来看一个例子:

1
2
3
4
5
6
>>> 'tes'+'t' is 'test'
>>> True

>>> a = 'tes'
>>> a + 't' is 'test'
>>> False

你可能会问为什么是这样?它们不都只是包含字母吗,没有空格应该是被主动intern的呀? 的确是不错,但是你忽略了一个事实。在第一个例子中,‘tes’ + ‘t’是在compile-time(编译时)求值的,被替换成了’test’,而在第二个例子中,a + ‘t’是在run-time(运行时)拼接的,导致没有主动触发intern机制。

我们再来看一下Python中的小整数的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> a = 257
>>> b= 257
>>> print(id(a), id(b))
94149312 94149488

>>> a = 256
>>> b = 256
>>> print(id(a), id(b))
1790200048 1790200048

>>> a = -5
>>> b = -5
>>> print(id(a), id(b))
1790195872 1790195872

>>> a = -6
>>> b =-6
>>> print(id(a), id(b))
94148784 94149296

在Python的小整数池[-5,256]这个范围内也是默认开启intern机制,也就是在创建对象的时候会先判断整数是否在小整数池中,是的话就共用同一个对象,否则就新建对象。

总结一下

1、is 比较的是地址,注意小整数池和字符串,一般重复创建的时候会指向同一个对象。
2、== 比较的是值,其实==重载了对象的__eq__方法,而这个方法比较的是对象的值。

3、单词,即Python标识符是不可修改的,默认开启intern机制,是共用对象,当引用计数为0时自动被回收。
4、字符串(包含了除Python标识符以外的字符),不可修改,默认没有开启intern机制,也是当引用计数为0时自动被回收。

5、极少数特殊情况下(如上述最后一个例子时),也不会主动开启intern机制。

6、在Python的小整数池[-5,256]这个范围内也是默认开启intern机制。