相等运算符(==)用来比较两个对象的内容(值)是否一致,身份运算符(is)用来比较两个对象的内存地址是否一致,即是否为同一对象。
来看下面的示例:
>>> a = 1
>>> b = 1
>>> id(a)
1678273568
>>> id(b)
1678273568
>>> a == b
True
>>> a is b
True
变量a、b都指向对象1,对象的值都相等,且对象的内存地址一致,所以a == b与a is b的运算结果都为True。
继续尝试:
>>> a = 256
>>> b = 256
>>> id(a)
1678281728
>>> id(b)
1678281728
>>> a = 257
>>> b = 257
>>> id(a)
50298224
>>> id(b)
50298768
>>> a == b
True
>>> a is b
False
奇怪的是,为何变量a、b指向对象256时,对象的内存地址相同,a == b与a is b的结果都为True,而当变量a、b指向对象257时,对象的内存地址不相同,a == b的结果为True,a is b的结果为False,为什么会出现这种现象呢?接下来看:
>>> a = -5
>>> b = -5
>>> id(a)
1678273376
>>> id(b)
1678273376
>>> a == b
True
>>> a is b
True
>>> c = -6
>>> d = -6
>>> id(c)
50298768
>>> id(d)
51007536
>>> c == d
True
>>> c is d
False
其中,让变量a、b指向对象-5,让变量c、d指向对象-6,仍出现了上述类似情况。
原因在于,考虑到小整数-5至257(不包含)会被频繁使用(如在for循环中的迭代次数),Python会在其启动后的运行时初始化过程中,一次性创建这些小整数对象,只会为它们分配一次内存,将它们缓存在小整数对象池(small_ints)之中,供共享使用,以提升整体性能,避免频繁分配和释放内存以及可能造成大量内存碎片。而在此范围之外的数字(如-6、257,以及浮点数),则会在每次对象创建时重新分配内存,所以内存地址也会不一样。例如:
>>> a = 1.0
>>> b = 1.0
>>> id(a)
3614160
>>> id(b)
3615336
类似于对小整数对象的处理,Python会对只包含英文字母、数字、下划线的字符串采取intern机制的处理,经过intern机制处理后的值相同的字符串对象,在Python解释器运行过程中只有唯一的一个,缓存至字符串缓冲池中,供共享使用,避免频繁创建和销毁字符串对象,提升执行效率。请看下面的示例:
>>> a = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789' #@01
>>> b = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789' #@02
>>> id(a)
50328720
>>> id(b)
50328720
变量a、b都是指向同一个字符串对象(为同一内存地址),该字符串对象只包含了英文字母、数字、下划线(见#@01、#@02)。接着看:
>>> a = 'Hello World' #@01
>>> b = 'Hello World' #@02
>>> id(a)
51069808
>>> id(b)
51070896
>>> a == b
True
>>> a is b
False
变量a、b所指向的字符串对象的值虽然相同,但由于包含了空格(见#@01、#@02),并非只是由英文字母、数字、下划线所组成,所以是两个不同的字符串对象。再看:
>>> a = '@' #@01
>>> b = '@' #@02
>>> id(a)
44265744
>>> id(b)
44265744
>>> c = '@1' #@03
>>> d = '@1' #@04
>>> id(c)
50973808
>>> id(d)
50973528
为何变量a、b指向的字符串对象@(见#@01、#@02,并非英文字母、数字或下划线)为同一内存地址,变量c、d指向的字符串对象@1(见#@03、#@04)则不是同一内存地址呢?
这是因为Python除了以intern机制处理和缓存以英文字母、数字、下划线组成的字符串,还会将长度为1的字符串对象缓存在专门的短字符串缓冲池(characters)之中,供共享使用。Python中内建的intern函数,可使值相同的字符串共用同一个字符串对象,避免为相同字符串重复分配内存空间,如下图所示:
接下来,我们再看看列表、元组、字典、集合使用相等运算符==与身份运算符is进行比较时的区别:
>>> # 对两个列表进行比较
>>> a = [1]
>>> b = [1]
>>> id(a)
50880840
>>> id(b)
50610888
>>> a == b
True
>>> a is b
False
为何变量a、b所指向的列表对象的值相同,却不是同一内存地址呢?这是因为列表为可变对象,Python会为每个列表分配内存。
>>> # 对两个元组进行比较
>>> a = (1)
>>> b = (1)
>>> id(a)
1451715616
>>> id(b)
1451715616
>>> a == b
True
>>> a is b
True
>>> c = (1, [1])
>>> d = (1, [1])
>>> id(c)
51154888
>>> id(d)
50417480
>>> c == d
True
>>> c is d
False
为何会出现上述结果?因为元组为不可变对象,当它只包含由不可变对象(如字符串)时,变量a、b所指向的对象相同,当它包含可变对象(如列表)时,变量c、d会指向不同的对象。
另外,再看看下面的代码:
>>> a = {1:'a'}
>>> b = {1:'a'}
>>> id(a)
50457408
>>> id(b)
51007424
>>> a == b
True
>>> a is b
False
>>> c = {1}
>>> d = {1}
>>> id(c)
50474568
>>> id(d)
50839112
>>> c == d
True
>>> c is d
False
可知,变量a、b指向的字典是可变对象,变量c、d指向的集合也是可变对象。