1. 不可变的PyIntObject
Python源码剖析 - 对象初探 我们对 PyIntObject 已经有了初步的了解。 Python 中的对象可以分为固定长度和可变长度两种类型。除此之外,也可以按照可变和不可变进行划分。
PyIntObject 则属于长度固定且不可变的对象。相比其他的对象而言,最简单,也最容易理解。
我们先来了解一下 PyIntObject 类型的类型信息,代码如下:
PyTypeObject PyInt_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "int", sizeof(PyIntObject), 0, (destructor)int_dealloc, /* tp_dealloc */ (printfunc)int_print, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)int_compare, /* tp_compare */ (reprfunc)int_to_decimal_string, /* tp_repr */ &int_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)int_hash, /* tp_hash */ 0, /* tp_call */ (reprfunc)int_to_decimal_string, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS, /* tp_flags */ int_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ int_methods, /* tp_methods */ 0, /* tp_members */ int_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ int_new, /* tp_new */ };
核心代码解释:
代码 | 说明 |
---|---|
PyVarObject_HEAD_INIT(&PyType_Type, 0) | 1. 设定ob_type指向PyInt_Type结构的地址 2.定长 |
"int" | 设定 tp_name |
int_dealloc | PyIntObject对象的析构函数 |
int_print | PyIntObject对象的标准输出函数 |
int_compare | 比较操作 |
int_to_decimal_string | 将整数转换为数字字符串 |
&int_as_number | 数学操作函数集合,如加减乘除等 |
int_hash | 计算该对象的hash值 |
int_methods | 对象成员函数的集合 |
2. PyIntObject对象创建三种方式
关于 PyIntObject 的对象创建过程,我们在Python源码剖析 - 对象初探中已经做了初步的介绍,通过阅读源码我们可以发现有有以下三种方式,可以用来创建 PyIntObject 对象
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int); PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int); PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
也就是说,一个 PyIntObject 可以来源于 String、Unicode 和 Long 类型的变量。
PyObject * PyInt_FromLong(long ival) { register PyIntObject *v; #if NSMALLNEGINTS + NSMALLPOSINTS > 0 if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { v = small_ints[ival + NSMALLNEGINTS]; Py_INCREF(v); #ifdef COUNT_ALLOCS if (ival >= 0) quick_int_allocs++; else quick_neg_int_allocs++; #endif return (PyObject *) v; } #endif if (free_list == NULL) { if ((free_list = fill_free_list()) == NULL) return NULL; } /* Inline PyObject_New */ v = free_list; free_list = (PyIntObject *)Py_TYPE(v); (void)PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }
从代码实现来看,如果是一个小整数,那么就直接增加对这个小整数对象的引用,否则,则需要从 free_list 中选取一个可用的对象,并将该对象的 ob_ival 设置为当前的数值。
接下来,我们详细介绍 PyIntObject 中对小整数的处理方式。
3. PyIntObject的小整数对象
在 Python 中,代码直接对小整数对象的范围进行了限定,即 [-5, 257)
#ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* References to small integers are saved in this array so that they can be shared. The integers that are saved are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; #endif
这些小整数对象,类似于常量一样常驻内存中,并不会不释放,这样做的优点在于:
- 使用效率高,这些小整数对象,像静态常量一样,直接拿来就可以用
- 避免经常使用小整数导致内存操作效率降低 - 假设我们没有将小整数常驻内存,按照 Python 中其他对象的处理方式来处理,必然会导致 malloc() 和 free() 的频繁调用,引起大量内存碎片,严重影响 Python 的整体性
但是这样做有几个的问题:
- 常量的设定,是一个经验值,你没办法预计在你的程序里,这个样的设置就是最优的
- 修改代价大,如果你发现我对小整数的范围进行调整,你能做的唯一办法,就是对源码进行修改,并进行重新编译。
4. PyIntObject的大整数对象
对于小整数,Python 通过小整数对象池的方式来解决效率问题,那么对于其他整数对象,又是如何处理的呢。
其实与小整数类似,也是通过内存池技术,不同的是这个内存池中的数值并不是固定的,而是谁需要使用,就来申请,使用完了,则归还到池子中去。
struct _intblock { struct _intblock *next; PyIntObject objects[N_INTOBJECTS]; }; typedef struct _intblock PyIntBlock; static PyIntBlock *block_list = NULL; static PyIntObject *free_list = NULL; static PyIntObject * fill_free_list(void) { PyIntObject *p, *q; /* Python's object allocator isn't appropriate for large blocks. */ p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock)); if (p == NULL) return (PyIntObject *) PyErr_NoMemory(); ((PyIntBlock *)p)->next = block_list; block_list = (PyIntBlock *)p; /* Link the int objects together, from rear to front, then return the address of the last int object in the block. */ p = &((PyIntBlock *)p)->objects[0]; q = p + N_INTOBJECTS; while (--q > p) Py_TYPE(q) = (struct _typeobject *)(q-1); Py_TYPE(q) = NULL; return p + N_INTOBJECTS - 1; }
通过 block_list
和 free_list
两个指针来进行维护,free_list
是一个单向列表,维护 block_list
中所有可用的内存块。如果 block_list
不够用了,则调用 fill_free_list()
申请新的内存。
5. 更多内容
原文来自兔子先生网站:https://www.xtuz.net/detail-136.html
查看原文 >>> Python源码剖析 - Python中的整数对象
如果你对Python语言感兴趣,可以关注我,或者关注我的微信公众号:xtuz666
来源:https://www.cnblogs.com/xtuz/p/12548887.html