python 内核分析(四):Python 整数类型对象

本系列作为本人@takooctopus深入学习python机制的记录,这个博客遵照着栖迟于一丘的博客上面的流程进行的,也包含我在实际查看源码时的感想,特此列出,表示感谢。

关于Python2 和 Python3

由于python2考虑了32位数,其分为了int和long,其中int会考虑溢出问题。我们在python3中对这个问题进行了修正,全部都使用了长整型。

我们来看一看这个类的实现:


{{- code -}}

我们将其中的属性做个表格

属性元信息 解释
long_dealloc 对象的析构操作
PyObject_Del 对象的释放操作
long_to_demical_string 转化为PyString对象
long_hash 获取hash值
long_richcompare 比较
long_as_number 数值比较
long_methods 成员函数

就在同一个文件[longobject.c]中,我们可以看见对其属性的具体定义,以属性比较long_richcompare()为例:


{{- code -}}

我们可以看见其比较两个整型对象大小直接使用了==运算符,当两个直接引用的同一个对象可以快速的判断。而在其他的情况下会引用long_compare()函数。

long_compare()函数也定义在[longobject.c]


{{- code -}}

这个比较第一步就是比较了两个PyObject的大小,最终通过sigh输出相对大小。
在位数相同时,依次比较各个位数。

但我们要明确一点,就是长整型long在python中是以int[]的柔性数组实现的。,其数组名为ob_digit[]

我们回看[Include/longintrepr.h]


{{- code -}}

我们看其注释:

  • 整数绝对值为SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
  • 其正负用ob_size的正负表示
  • 0值使用ob_size == 0
  • 正常的整数ob_digit[abs(ob_size)-1]不为0
  • 对于所有有效的i,其ob_digit[i]范围在[0,MASK]之间

我们简单的表示一下,当ob_size=3,ob_digit=[4,2,1],SHIFT=30,MASK=2^30-1时:

\begin{align}
digit &= 4*{( 2^{ 30 } )}^0 + 2*{( 2^{ 30 } )}^1 + 1*{( 2^{ 30 } )}^2 \\
&= 1152921506754330628
\end{align}

可以看出是一个进制转换过程。

作为一个数值对象,我们首先得实现它的数值运算操作long_as_number


{{- code -}}

加法运算

我们不妨从最基础的加法看起:


{{- code -}}

更为具体的实现在x_add()x_sub()中:


{{- code -}}

我们可以看见每步都使用了掩码,我们去找PyLong_MASKPyLong_SHIFT的具体值。


{{- code -}}

PyLong_SHIFT应该为5的倍数。
PyLong_MASK的值为0b111111111111111111111111111111「30个1」

我们在此能看见python保存整型中,ob_digit[]中位数为30位,略小于理论上int的32位。「在有些版本是15位,不知道现在30位的好处在什么」

carry作为了一般的进位处理和中间结果。

z作为最终的结果变量,但最开始申请空间时默认比两个加数的空间大一,最终需要进行空间的整理long_normalize()


{{- code -}}

Py_size[Include/object.h]中用宏定义


{{- code -}}

即通用地调整了数组的ob_size

乘法运算

同样的,我们来看乘法相关运算:

乘法运算同样定义在[Objects/longobject.c]


{{- code -}}

我们看当两个数的乘积能用long long类型保存时,即ob_size均小于1时,使用简单的两数相乘。再将其转化为PyLong_Type。如果整数过大,就使用k_mul计算,采用Karatsuba multiplication算法。


{{- code -}}

上面是简单的实现,具体的可以看wikipedia的介绍或者我相关的博文

小整数

特别的,在整数范围内,有一些整数被视为小整数NSMALLINTS,其范围在[-5,257)中,保存于small_ints[]这个数组中。

其最主要的是,在小整数池中的数,都会给出同一个引用。


{{- code -}}

特别的,对象池初始化在_PyLong_Init()函数中:


{{- code -}}