用过Java的人可能都了解过它的内存管理机制,今天我们也来看看Python的内存管理,首先让我们从最基础的对象入手,了解==、is和id()。==是我们最常用的运算符,它的作用是检查两个对象包含的值是否一致,当我们写a==b时,python会比较两个对象的实际数据,这是一种深度比较,有时候还会调用对象的特殊方法__eq__(),让我们看个简单的例子:# == 比较的是对象的值a = [1, 2, 3]b = [1, 2, 3]print(a == b) # True - 两个列表的内容完全相同
在底层来看,==可能触发更复杂的操作,对于列表,它会逐一比较每个元素的值,对于自定义对象,他会调用我没重写的__eq__方法,实现自定义的相等逻辑。同样,我们看个自定义对象的例子:class SensorValue: def __init__(self, value, tolerance): self.value = int(value) # 传感器数值 self.tolerance = int(tolerance) # 误差范围 def __eq__(self, other): # 自定义相等逻辑:两个数值在彼此的误差范围内,就认为相等 return abs(self.value - other.value) <= min( self.tolerance, other.tolerance )v1 = SensorValue(100, 3) # 数值100,误差±3v2 = SensorValue(97, 5) # 数值97,误差±5v3 = SensorValue(96, 10) # 数值96,误差±10print(f"v1 == v2: {v1 == v2}") # True - 97在100±3范围内,100也在97±5范围内print(f"v1 == v3: {v1 == v3}") # False - 96不在100±3范围内
和==不同,is检查的是对象身份,也就是两个变量是否只想内存中的同一个对象,而不是判断值是否相同。# `is` 比较的是对象身份(是否指向同一个内存地址)a = [1, 2, 3]b = a # b 直接引用a指向的列表对象,没有创建新对象c = [1, 2, 3] # 重新创建一个列表,内容和a相同,但内存地址不同print(a is b) # True - a和b指向同一个对象print(a is c) # False - a和c值相同,但指向不同对象
它的逻辑是,当我们写a is b时,Python会直接比较两个对象的内存地址,而不做任何复杂的数值比对,所以在判断None、True 、False时,推荐使用is 而不是 ==,原因是None、True、False都是Python中的“单例对象”,整个程序运行期间,它们只会存在一个实例,所以用is更高效。id()函数的作用是返回一个对象的虚拟内存地址,以整数形式表示。这个整数,就是is运算符底层用来比较的指针值,还是用例子来看:a = [1, 2, 3]b = aprint(f"{a is b=}") # True - 指向同一个对象print(f"{id(a) == id(b)=}") # True - 内存地址相同,等价于 a is bprint(f"{id(a)=}") # 输出a的内存地址(整数形式)print(f"id(a)=0x{id(a):x}") # 将内存地址转为十六进制(更符合我们对地址的认知)
接下来我们用id()做几个小的测试,看看Python的内存管理x = 4y = 2**2 # 2的平方,结果也是4print(f"{x is y=}") # True - 居然是同一个对象!print(f"{id(x)=}")print(f"{id(y)=}") # 和x的内存地址完全相同
上面的代码是否发现了个问题?x和y指向了同一个对象,这是因为Python为了优化性能,会对小整数(-5~256)进行“缓存”,将它们作为单例存储到内存中。那如果我们超出这个范围会怎样呢?x, y = 2*123, 2**123 # 2*123=246,2**123是一个大整数print(f"{x is y=}") # False - 不同对象!print(f"{hex(id(x))=}") # 十六进制内存地址print(f"{hex(id(y))=}") # 和x的地址不同
很明显,不在这个范围的大整数没有缓存机制,每次都会创建一个新的整数对象。x = y = 2**123 # 连续赋值,计算一次2**123print(f"{x is y=}") # True!print(f"{hex(id(x))=}") print(f"{hex(id(y))=}") # 和x的地址相同
这次在不在意料之中呢?没存这次它们又指向同一块内存了......上面我都没有贴输出结果,有兴趣的不妨自己动手看看输出结论吧,下次见。