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