Python 中的 magic int

TL;DR: [-5, 257) 范围内的整数,CPython 做了优化,内存中只会有一份,使用单例模式


在 Python 中判断对象相等性时,会经常使用 ==is 两种判断方法

is 如其字面意思,判断两个对象是不是同一个对象,比较对象的内存地址,相当于调用 hex(id(a)) == hex(id(b))

对于 == 而言,一般继承自 object 的对象默认都是比较的内存地址,和 is 没有区别 但是对象内部实现时可以通过覆盖 __eq__ 方法,自定义想要的比较效果:

 1class Person:
 2	def __init__(self, name, age):
 3    	self.name = name
 4    	self.age = age
 5
 6	def __eq__(self, other):
 7    	return abs(self.age - other.age) < 1
 8
 9	def get_info(self):
10    	return self.name, self.age
11
12Person.__eq__ = lambda x, y: abs(x.age - y.age) < 1
13a = Person("Dan", 17.5)
14b = Person("Link", 18)
15print(a == b)      # turns out True

也许有些时候对象是封装得比较好的,并不方便修改,这种时候可以通过 lambda 表达式修改对象方法达到同样的效果:

 1class Person:
 2    def __init__(self, name, age):
 3        self.name = name
 4        self.age = age
 5
 6    def get_info(self):
 7        return self.name, self.age
 8
 9Person.__eq__ = lambda x, y: abs(x.age - y.age) < 1
10a = Person("Dan", 17.5)
11b = Person("Link", 18)
12print(a == b)

比如在 LeetCode 刷题时,ListNode.__lt__ = lambda a, b: a.val < b.val 然后就可以直接 mode_list.sort() 排序

is 除了保持代码的可读性之外,按照 PEP8 的建议,也经常用来比较单例对象,最常见的就是 if node is None:

了解以上内容,其实可以尝试回答一下比较诡异的一个问题了:

1In [1]: a = 420
2In [2]: b = 420
3In [3]: a is b      # shit happens
4Out[3]: False
5In [4]: a == b
6Out[4]: True

其实是 Python 的实现上的一个优化 首先 Python 已经没有 int 类型了,使用的都是 long 型,相比于原来的 int 类型,占用了 2 倍的空间 所以对于使用比较频繁的比较小的整数,做成了单例的形式。 可以参考 CPython longobject 的实现

 1#ifndef NSMALLPOSINTS
 2#define NSMALLPOSINTS           257
 3#endif
 4#ifndef NSMALLNEGINTS
 5#define NSMALLNEGINTS           5
 6#endif
 7#if NSMALLNEGINTS + NSMALLPOSINTS > 0
 8/* Small integers are preallocated in this array so that they
 9   can be shared.
10   The integers that are preallocated are those in the range
11   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
12*/
13static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

[-5, 257) 范围内的整数,内存中只会有一份,不会重复分配内存

对于之前的测试代码,a 与 b 已经超出范围,所以占用了不同的内存地址,is 判断为 False; 但是 __eq__ 实现中,比较的是值的相等性,判断为 True