本文会深入探究 在Python 内部整数对象是如何实现的。
在 Python 内部,一个整数对象是用 PyIntObject 结构来表示的,该整数对象的值属性为 long 型。
1 2 3 4 | typedef struct { PyObject_HEAD long ob_ival; } PyIntObject; |
为了避免每次要用到整数对象的时候都要分配一个新的内存,Python 预先为一批尚未使用的空闲整数对象分配了一块内存。
下面这个结构就是 Python 用来给整数对象分配内存的,这些整数对象又叫作 PyIntObjects 。该结构初始化完成之后,当 Python 脚本中为新的对象赋值的时候就可以直接使用之前分配好的整数对象了。这个结构叫做“PyIntBlock”,定义如下所示:
1 2 3 4 5 | struct _intblock { struct _intblock *next; PyIntObject objects[N_INTOBJECTS]; }; typedef struct _intblock PyIntBlock; |
Python 在给一批整数对象分配内存块时,这些对象实际上并没有被赋值。我们把这些对象叫做“待用的空闲整数对象”。在 Python 程序中,当一个新的整数值被使用时,这个值就会被赋给下一个可用的整数对象。因为空闲整数对象被赋值的过程中不需要内存分配,所以速度很快。
在同一个块内部的这些整数对象是通过叫做“ob_type”的内部指针从后往前倒序链接在一起的。这里需要注意的是,Python 的源码中存在对内部指针的滥用情况,所以对于指针的名字不用太过纠结。
每一个整数块包含了k个整数对象,k等于 1 KB 的内存块可以容纳的整数对象的数目,在我 64 位电脑上大概是 40 个 PyIntObject 对象。当这个块中所有的整数对象都用完了的时候,就会分配一块新的内存给新的整数对象列表。
已经分配的整数对象内存块是通过一个单向链表来记录的。在Python内部这个列表叫做“block_list”。
在 Python 中使用了一种特殊结构提前为一部分小整数分配了空间,以便快速访问。这是一个包含 262 个指向整数对象的指针的数组(在下文会称之为小整数数组)。这些小整数对象会在(前面提到的)整数对象块进行初始化时被分配,它们的范围是 -5 到 256。许多 Python 程序会频繁使用这一范围内的整数,所以这种预处理的办法非常巧妙。
1 2 3 | #define NSMALLPOSINTS 257 #define NSMALLNEGINTS 5 static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; |
值为 -5 的整数对象在这个小整数数组中偏移量为0,也就是说位于数组的第一个位置,值为 -4 的整数对象偏移量就是 1,以此类推。
试想一下,在 Python 脚本中定义下面一个整数会发生什么?
1 2 3 | >>> a=1 >>> a 1 |
当你执行第一行的时候,就调用了 PyInt_FromLong 函数,函数逻辑如下所示:
1 2 3 4 5 6 7 8 9 | if integer value in range -5,256: return the integer object pointed by the small integers array at the offset (value + 5). else: if no free integer object available: allocate new block of integer objects set value of the next free integer object in the current block of integers. return integer object |
在我们这个例子中,整数 1 由小整数数组中第 1 + 5 = 6 个指针所指。函数返回指向该整数对象的指针,然后变量“a”就会指向这个整数对象。
让我们看一下另外一个例子:
1 2 3 | >>> a=300 >>> a 300 |
300 并没有在小整数数组的范围内,所以就需要把一个空的整数对象赋值为 300。
如果你看过 Python 2.6 源码中的 intobject.c 文件,你就会看到许多处理相加、相乘、转换等运算操作的函数。比如说下面这个比较函数:
1 2 3 4 5 6 7 | static int int_compare(PyIntObject *v, PyIntObject *w) { register long i = v->ob_ival; register long j = w->ob_ival; return (i < j) ? -1 : (i > j) ? 1 : 0; } |
整数对象的值储存在对象的 ob_ival 属性中,类型为 long。每个值都存放在一个寄存器中以优化存取过程,所以比较操作是在两个寄存器之间完成的。如果 v 指向的整数对象小于 w 指向的整数对象,返回 -1;反之则返回 1。相等的情况下返回 0。