文章目录
在本章中我会先讲述一下在Python2中整数对象的实现原理,然后在解析一下Python2和Python3在整数对象中的原理上的差异。
1、PyIntObject
在Python中 “整数”这个概念的实现通过PyIntObject 对象来完成的,在Python的对象体系中分为两个大类,一类是 “可变对象”, 一类是 “不可变对象”。而整数对象就属于不可变的对象。所谓不可变对象是指,一旦这个对象被创建后,就不能够再改变它的值了。
不管在哪种语言中,整数的应用是非常广泛,这也意味着它的创建和销毁也会非常频繁,我们知道Python中对对象的管理主要采用引用计数的机制,也就是说如此频繁的创建和销毁会消耗太多的系统性能,因为我们知道Python是有C实现的,C对堆内存的管理是通过两个函数接口实现的,malloc和free, 这个两个函数直接包装了操作系统的核心接口,即意味着系统将频繁地在用户态和核心态之间切换。那么Python是如何来处理整数对象的呢?先看看静态的整数对象在底层的定义------PyIntObject
// intobject.h
typedef struct {
Pyobject_HEAD
long ob_ival;
} PyIntObject;
可以看见PyIntObject实际上就是对C整数类型long的的简单包装,通过对整数对象的源码可以知道,例如加法操作,加法操作执行后,确实没有改变参与操作的对象而是重新创建了一个新的PyIntObject对象,也就可以证明它确实是不可变的对象。
2、PyIntObject对象的创建和维护
PyObject* PyInt_FromLong(long ival)
PyObject* PyInt_FromString(char* s, char** pend, int base)
#ifdef Py_USING_UNICODE
PyObject* PyInt_FromUnicode(Py_UNICODES *s, int length, int base)
#endif
可以看见分别是从long值,从字符串以及Py_UNICODES对象生成,我们这里只看通过long值生成的方式,因为其他两种方式本质上利用了一些设计模式的思想进行了接口转换。
为了能够更加清晰地理解PyIntObject的创建过程,我们需要先了解Python中对象在内存的组织方式,现在我们就通过源码看看Python中的整数对象系统的结构。
-
2.2 小整数对象
在编程中,我们可能经常用到比较小的整数,例如0、1、5等等,可以想象C语言中for循环。在Python中所有的对象都存在于系统堆内存中,上面我们也说这些对象的操作是会影响整体性能的。所以在Python中,对于小整数对象的处理引入了对象池的技术,由于整数对象是不可变的对象,也就意味着对象池里的每一个对象都能够被共享。实际上这个小整数对象池在C的源码中被定义为 PyIntObject* , 这个小整数集的范围在[-5, 257)之间,在源码中可以去修改这个数值,但是修改后你将重新编译Python。
对于小整数对象,Python将这些对应的PyIntObject直接缓存在内存中。那么对于大整数的处理呢?
-
2.3 大整数对象
小整数对象完全缓存在对象池中,对于其他整数,Python运行环境将提供一块内存空间,这些内存空间由这些大整数轮流使用,换句话 说,谁需要就谁使用,这样避免了不断malloc申请堆内存带来的开销又那能够考虑效率。
在Python中,有一个PyIntBlock的结构,在这个结构的基础上,实现了一个单向链表,先看看源码中的定义: