在Python里试验一下下面的代码:
1 | a = 1 |
输出的结果令人咋舌,竟然会是True
、True
、False
,是什么原因导致这样的差异呢?
在Python源码中可以找到Python整数的定义(Include/intobject.h
):
1 | typedef struct { |
Python会将一部分常用的整数型对象缓存到内存中,以加快运行效率(空间换时间)。在Python源码中有如下代码(Objects/intobject.c
):
1 | struct _intblock { |
上面即为为整数对象分配内存的结构,整数对象即为上面的PyIntObject
结构体。当在Python代码中为新的整数做赋值操作的时候,实际上就是使用了这个PyIntBlock
结构体为一批整型变量申请了内存,剩下未使用的空间称为待用的空闲整数对象,在空闲整数对象赋值的过程中不需要分配内存空间,只有每个PyIntObject
都有值之后才会再次申请内存,所以效率上会很高。每个PyIntBlock
可存储的整数数量为N_INTOBJECTS,其定位为:
1 |
对于已经分配的整数对象,通过一个单向链表连接起来,在Python中,这个链表叫做block_list
, 其定义在Objects/intobject.c
中:
1 | static PyIntBlock *block_list = NULL; |
整个过程大致是这样的:
Python中使用了一种特殊的结构将一部分整数提前缓存到了内存中,这些整数在block_list
初始化的时候就分配了空间。由于程序会经常使用到这部分整数,这样的预处理可以加快程序运行时效率。这个结构叫做small_ints
,定义在Include/intobject.h
中:
1 |
|
这个结构保存了-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS
之间也即是[-5, 257)
之间的整数。如图:
如上,值为-5的整数位于small_ints中的第一位,就是说-5的偏移量为0,对应的,-4的偏移量为1,-3的偏移量为2…….
那么,当我们在运行num = -5
的时候,实际上在运行什么?
在Python的实现机制中,上面的代码我们将会调用PyInt_FromLong
函数,大概的逻辑是这样的:
1 | def PyInt_FromLong(n): |
这段逻辑对应的C代码如下(Python2.7.10):
1 | PyObject * |
执行a = -5
时,-5在small_ints范围[-5, 257)
内,于是会返回第-5 + 5 = 0
个缓存好的对象(的指针)。同理,对于开头的a = -6
,由于-6没有处于small_ints范围之间,就需要在PyIntBlock中分配一个新的PyIntObject对象,为其赋值为-6,并返回其指针。
于是,开头的Python代码结果为True
、True
、False
,就变得容易理解了。
如上~~~