内置int类型关联的数据结构包括:int数据对象、int类型对象。PyIntObject结构体用来表示int数据对象,PyInt_Type是int类型对象(PyTypeObject)。
PyIntObject
先看PyIntObject结构体的定义:
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
其中PyObject_HEAD是PyObject的数据部分(占16字节),加上int数据对象的数值字段(ob_ival),共20字节(正如上一节所见)。
由此可见int数据对象是定长对象(固定长度)。
PyInt_Type
再看PyInt_Type的定义:
PyTypeObject PyInt_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) // ob_type == &PyType_Type "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 */ ... int_new, /* tp_new */ };
其中"PyVarObject_HEAD_INIT(&PyType_Type, 0)",将PyInt_Type对象中的ob_type字段设置为PyType_Type对象的地址(即指向PyType_Type对象)。
int数据对象包括数值运算,数值运算操作的定义在int_as_number部分。
PyIntObject创建
[Python] >>> x=200 >>> y=int(‘2000‘) >>> type(y) <type ‘int‘>
"x=200"会创建一个int数据对象,会直接用整数来创建PyIntObject对象(PyInt_FromLong)。
"y=int(‘2000‘)"也会创建一个int数据对象,但它是通过PyInt_Type中定义的tp_new函数(int_new)来显式创建,在int_new中会根据传递参数的类型,分别通过PyNumber_Int(浮点数)、PyInt_FromLong(整数)、PyInt_FromString(字符串)、PyInt_FromUnicode(Unicode字符串)函数,对参数进行处理最终会调用PyInt_FromLong来创建PyIntObject对象。
所以就直接来看PyInt_FromLong函数的定义:
PyObject * PyInt_FromLong(long ival) { register PyIntObject *v;
// (1)小整数 #if NSMALLNEGINTS + NSMALLPOSINTS > 0 if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { v = small_ints[ival + NSMALLNEGINTS]; Py_INCREF(v); return (PyObject *) v; } #endif
// (2)通用整数对象空闲列表 if (free_list == NULL) { if ((free_list = fill_free_list()) == NULL) return NULL; } // (3)取用和初始化整数对象 v = free_list; free_list = (PyIntObject *)Py_TYPE(v); (void)PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }
(1)小整数
NSMALLNEGINTS宏表示(负)小整数数目,NSMALLPOSINTS宏表示(正)小整数数目。
如果开启了小整数对象缓存,并且ival在小整数的范围内(-5 ~ 256),直接从小整数对象数组(samll_ints)中返回PyIntObject对象。
#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
因为小整数被使用的频率非常高,特别是循环中和临时变量,采用小整数对象数组可以达到复用的目的。小整数数组的长度或许可以根据需要进行调整,即改变NSMALLNEGINTS和NSMALLPOSINTS宏定义。
(2)通用整数对象空闲列表
除小整数之外的通用整数对象,会从空闲列表中返回,如果空闲列表为空(NULL)则需要先填充它。
(3)取用和初始化整数对象
取用空闲列表首对象,并将空闲列表指针后移(ob_type字段用作链表指针来指向下一个空闲对象)。对于空闲列表的维护后面做进一步讲解。
被取用的整数对象,需要给ob_ival字段赋值,同时将ob_type指向类型对象PyInt_Type。可见ob_type字段在不同情况下用作不同用途。
下面演示小整数对象和通用整数对象的区别:
[Python]
>>> a=257 >>> b=257 >>> id(a) 17697476 >>> id(b) 17697436 # 同数值通用整数对象地址不同 >>> c=256 >>> d=256 >>> id(c) 22820468 >>> id(d) 22820468 # 同数值小整数对象地址相同
填充空闲列表的功能在fill_free_list函数中定义:
static PyIntObject * fill_free_list(void) { PyIntObject *p, *q; // (1)通用整数对象块创建 p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock)); if (p == NULL) return (PyIntObject *) PyErr_NoMemory(); ((PyIntBlock *)p)->next = block_list; block_list = (PyIntBlock *)p; // (2)块内整数对象串联 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; }
(1)通用整数对象块创建
每次填充空闲列表是以块为单位进行的,PyIntBlock中定义了块指针和PyIntObject对象数组(1K内存以内的数组),具体见代码。
#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */ #define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */ #define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject)) struct _intblock { struct _intblock *next; PyIntObject objects[N_INTOBJECTS]; }; typedef struct _intblock PyIntBlock;
(2)块内整数对象串联
将PyIntBlock内整数对象数组中的各个对象,从后往前将整数对象串联起来(ob_type字段),方便取用和回收。将PyIntBlock中最后的PyIntObject对象指针返回。
PyIntObject销毁
PyInt_Type对象中定义了对象销毁函数(int_dealloc),看函数定义:
static void int_dealloc(PyIntObject *v) { if (PyInt_CheckExact(v)) { // (1)插入空闲列表 Py_TYPE(v) = (struct _typeobject *)free_list; free_list = v; } else Py_TYPE(v)->tp_free((PyObject *)v); }
(1)插入空闲列表
PyIntObject对象销毁的时候不是直接释放内存,而是将其插入空闲列表(free_list),这样就和创建过程能够联系起来。
另外的分支表示作为整数的派生类对象,通过PyTypeObject对象中的tp_free操作完成销毁。
小整数对象池(small_ints)和通用整数对象池(free_list)的使用能够达到PyIntObject对象复用的目的,减少PyIntObject对象创建,但会增加部分内存占用。
下面演示PyIntObject对象创建和销毁过程中free_list的变化。
PyIntObject加法运算
整数运算操作在int_as_number部分定义,可以查看其源码:
static PyNumberMethods int_as_number = { (binaryfunc)int_add, /*nb_add*/ (binaryfunc)int_sub, /*nb_subtract*/ (binaryfunc)int_mul, /*nb_multiply*/ (binaryfunc)int_classic_div, /*nb_divide*/ (binaryfunc)int_mod, /*nb_remainder*/ (binaryfunc)int_divmod, /*nb_divmod*/ (ternaryfunc)int_pow, /*nb_power*/ (unaryfunc)int_neg, /*nb_negative*/ (unaryfunc)int_int, /*nb_positive*/ (unaryfunc)int_abs, /*nb_absolute*/ (inquiry)int_nonzero, /*nb_nonzero*/ (unaryfunc)int_invert, /*nb_invert*/ (binaryfunc)int_lshift, /*nb_lshift*/ (binaryfunc)int_rshift, /*nb_rshift*/ (binaryfunc)int_and, /*nb_and*/ (binaryfunc)int_xor, /*nb_xor*/ (binaryfunc)int_or, /*nb_or*/ int_coerce, /*nb_coerce*/ (unaryfunc)int_int, /*nb_int*/ (unaryfunc)int_long, /*nb_long*/ (unaryfunc)int_float, /*nb_float*/ (unaryfunc)int_oct, /*nb_oct*/ (unaryfunc)int_hex, /*nb_hex*/ 0, /*nb_inplace_add*/ 0, /*nb_inplace_subtract*/ 0, /*nb_inplace_multiply*/ 0, /*nb_inplace_divide*/ 0, /*nb_inplace_remainder*/ 0, /*nb_inplace_power*/ 0, /*nb_inplace_lshift*/ 0, /*nb_inplace_rshift*/ 0, /*nb_inplace_and*/ 0, /*nb_inplace_xor*/ 0, /*nb_inplace_or*/ (binaryfunc)int_div, /* nb_floor_divide */ (binaryfunc)int_true_divide, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ (unaryfunc)int_int, /* nb_index */ };
上面这些操作都是常用的数值运算,这里只针对加法操作进行分析,查看int_add函数源码:
static PyObject * int_add(PyIntObject *v, PyIntObject *w) { register long a, b, x; // (1)数值运算 CONVERT_TO_LONG(v, a); CONVERT_TO_LONG(w, b); x = (long)((unsigned long)a + b); if ((x^a) >= 0 || (x^b) >= 0) return PyInt_FromLong(x); // (2)创建新PyIntObject对象 // (3)溢出时创建PyLongObject对象 return PyLong_Type.tp_as_number->nb_add((PyObject *)v, (PyObject *)w); }
(1)数值运算
(2)创建新PyIntObject对象
如果加法运算的结果没有溢出,则会创建新的PyIntObject对象来存放结果并返回。
(3)溢出时创建PyLongObject对象
int数据对象是定长对象,同时它也是不可变对象(与直观印象相反)。对一个int变量的重新赋值,其实包括销毁原对象和创建新对象两步。以下是演示代码:
>>> a=258 >>> id(a) 17697476 >>> a=259 >>> id(a) 17697436
定长不可变对象类型还有float、long。
原文:https://www.cnblogs.com/carlliu3/p/14641475.html