接着之前的 《Python 对象模型》继续看看 Python 的源代码,之前说了,Python 的一切数据都是对象,甚至于不是数据都是对象,例如函数啊,类啊这些这些对象。其实,在 Python 的实现中,套路都差不多,无非是关注这些对象是怎么创建的,然后内部的数据结构是怎么编排的,有什么优化策略这些,所以,我就准备不多看,就看看整数的实现。
对于 Python 整数,我开始学的时候有个很感兴趣的点就是 Python 的整数居然是没有溢出的,例如这些代码你就可以看出来了:
有意思吧,一个整数左移了 100 位都没溢出,我想你肯定不会说内部是用一个 128 位的数据类型来实现的,好像现在 C 语言中也还没这个类型吧?
整数的内部结构
之前一篇文章说过啦,整数的数据结构是 PyLongObject,没说也至少提到过,所以我就直接看这个数据结构好啦:
很简单,可以看到数据是保存到了一个指针(这里定义的是一个元素为1的数组,似乎是 C 语言里面的惯用套路)里头,然后这个数据长度是放在 PyOBject_VAR_HEAD 里面的啦,后面会看到关于他的操作。
新建和删除一个整数
新建和删除一个整数在 Python 里面是有一点点优化的,类似于 Java,对于小整数,Python 会在启动的时候先创建好,后面要用的时候就不用创建了,直接增加引用计数即可,反正你也改变不了一个整数的值(不可变对象),至于小整数的定义,看代码中的这个值:
这里也就是将 [-5, 257) 范围内的整数都定义为小整数。所以创建的时候,如果数字是这个返回就直接返回已经创建好的对象了:
这是定义的一个检查小整数的宏,在创建整数对象的时候都会先检验一遍,然后不是小整数才会做其他整数的操作:
这里 Line 44 就是检查并且返回小整数的宏,实现就很简单了,再来看看其他整数是怎么操作的,我们前面看到了,整数都是被拆到一个个 digit 里面的,digit 的大小不确定,可能是 15 bit 或者 30 bit。所以这里第一步是先确定需要多少个 digit,Line49 就是在计算,然后 Line 52 就创建对应长度的对象,因为我们说了,digit 是一个数组,需要调节它的长度。然后 Line 58 就是拆分保存数据了。
这里还有一点没高亮的是 Line 55,这里将这个整数的正负保存在 ob_size 这个字段,而不是保存在 digit 里面,也就是说 digit 里面保存的是数字的绝对值,这样有什么好处?操作方便啊,加减乘除都不需要担心符号的问题,而且拆分也是大胆得拆,不需要考虑符号问题。
整数的加减乘除
整数这么创建的话,加减我们就比较简单得可以猜到了,就是直接加和直接减少,来点简单的代码吧:
可以看到,就两个循环,一个个 digit 得加,唯一需要注意的就是 长度 和 进位 的问题。加减是简单,但是乘除就复杂了,乘法的我还能看得明白一些,除法的我直接就放弃了,很复杂,引用注释的一段话:
/* We follow Knuth [The Art of Computer Programming, Vol. 2 (3rd edn.), section 4.3.1, Algorithm D], except that we don't explicitly handle the special case when the initial estimate q for a quotient digit is >= PyLong_BASE: the max value for q is PyLong_BASE+1, and that won't overflow a digit. */
那就记录点我看懂了的乘法吧,因为它将整数拆成一个个 digit,所以乘法也就是一个个 digit 来乘,我们可以想象一下小学的时候学的乘法:
这是 10进制的嘛,然后我们可以将一个 digit 当成一位,也是可以实现的,但是,就是更复杂了一些,为了减少一些乘法操作,Python 进行了一点点的优化:
/* (ah*X+al)(bh*X+bl) = ah*bh*X*X + (ah*bl + al*bh)*X + al*bl * Let k = (ah+al)*(bh+bl) = ah*bl + al*bh + ah*bh + al*bl * Then the original product is * ah*bh*X*X + (k - ah*bh - al*bl)*X + al*bl * By picking X to be a power of 2, "*X" is just shifting, and it's * been reduced to 3 multiplies on numbers half the size. */
这里的目的就是将大的整数的乘法转化成小一半的整数的乘法,从而减少真正乘法的规模和次数,同时,这里的 X 因为是 2 的倍数,所以直接移位就可以了,效率不能更高,所以真正的乘法就变少了。