作为 Python 中最简单的对象,整数对象是研究 Python 对象体系的一个非常好的切入点。直观上 会认为整数对象的实现非常简单,如果单纯以整数对象而言,实现确实非常简单。然而在 Python 中,为了运行效率,实际上存在着一个以缓冲池为核心的整数 对象的体系结构,实际上,Python 各种对象几乎都拥有这样一个以缓冲池为核心的体系结构,理解这一点对 Python 运行时行为的了解有重要的意义。对 这种结构的深入挖掘也是本章的重点所在。
本章分为三个部分:
本文是第一部分。
在 Python 中,整数对象是最简单的对象了。对于读者来说,也是最容易真切地感受Python的对象机制的切入点,因此我们对Python所有内建对象的研究就从这个最简单的整数对象开始。
Python 中的整数是通过 PyIntObject 对象来实现的。PyIntObject 的对象是一个不变(immutable)对象,也就是说,在创建了一个PyIntObject的对象之后,我们就再也不能改变该对象的值了。在 Python 中,除了整数对象之外,还有很多对象也是不变对象,比如字符串对象等。
首先,我们先来关注一下 PyIntObject 的定义:
[intobject.h]
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
可以看到,Python中的 PyIntObject 实际上就是对C中long的一个简单包装。从对Python的对象机制的描述中,我们知道,对于Python中的对象,与对象相关的有用的元信息实际上都是保存在与对象对应的类型对象中的,对于PyIntObject,它的类型对象就是PyInt_Type:
[intobject.c]
PyTypeObject PyInt_Type = {
PyObject_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_repr, /* 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_repr, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* 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 */
0, /* 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 */
(freefunc)int_free, /* tp_free */
};
可以看到,在 PyInt_Type 中保存着 PyIntObject 的元信息,其中有 PyIntObject 对象的大小,PyIntObject的文档信息,更多的是PyIntObject所支持的操作。在下面的列表中,列出了 PyIntObject 所支持的操作:
| int_dealloc | 删除 PyIntObject对象 |
| int_free | 删除 PyIntObject对象 |
| int_repr | 转化成 PyString对象 |
| int_hash | 获得 HASH值 |
| int_print | 打印 PyIntObject对象 |
| int_compare | 比较操作 |
| int_as_number | 数值操作 |
| int_methods | 成员函数 |
我们可以看一看比较操作的代码:
[intobject.c]
static int int_compare(PyIntObject *v, PyIntObject *w)
{
register long i = v->ob_ival;
register long j = w->ob_ival;
return (i < j) ? -1 : (i > j) ? 1 : 0;
}
可以看到,PyIntObject对象实际上就是简单地将它所包装的long值进行比较。
需要特别注意的是int_as_number这个域,实际上它是一个PyNumberMethods结构体:
[intobject.c]
static PyNumberMethods int_as_number = {
(binaryfunc)int_add, /*nb_add*/
(binaryfunc)int_sub, /*nb_subtract*/
(binaryfunc)int_mul, /*nb_multiply*/
……
(binaryfunc)int_div, /* nb_floor_divide */
int_true_divide, /* nb_true_divide */
0, /* nb_inplace_floor_divide */
0, /* nb_inplace_true_divide */
};
在object.h中,可以找到PyNumberMethods的定义。在Python2.4中,PyNumberMethods中一共有38个函数指针,也就是说在其中定义了38种操作的信息,这些都是作为一个数值(Number)类型的对象可以向世界提供的操作,比如,加法,减法,乘法,模运算等等。
在int_as_number中,确定了对于一个整数对象,这些数值操作应该如何进行。当然,在PyNumberMethods的38中数值操作中,并非所有的操作都要求一定要被实现,在int_as_number中我们就可以看到,有相当多的操作是没有实现的。我们可以看一下PyIntObject中加法操作是如何实现的:
[intobject.h]
/* Macro, trading safety for speed */
#define PyInt_AS_LONG(op) (((PyIntObject *)(op))->ob_ival)
[intobject.c]
#define CONVERT_TO_LONG(obj, lng) \
if (PyInt_Check(obj)) { \
lng = PyInt_AS_LONG(obj); \
} \
else { \
Py_INCREF(Py_NotImplemented); \
return Py_NotImplemented; \
}
static PyObject *
int_add(PyIntObject *v, PyIntObject *w)
{
register long a, b, x;
CONVERT_TO_LONG(v, a);
CONVERT_TO_LONG(w, b);
x = a + b;
if ((x^a) >= 0 || (x^b) >= 0)
return PyInt_FromLong(x);
return PyLong_Type.tp_as_number->nb_add((PyObject *)v, (PyObject *)w);
}
如你所想,PyIntObject实现的加法操作是直接在其维护的long值上进行的,可以看到,在完成了加法操作后,还进行了溢出的检查,如果没有溢出,就返回一个新的PyIntObject,这个PyIntObject所拥有的值正好是加法操作的结果。这里清晰地显示了,PyIntObject是一个Immutable的对象,因为在操作完成之后,原来参与操作的任何一个对象都没有发生改变,取而代之的,一个全新的对象诞生了。而如果加法的结果有溢出,那么结果就再不是一个PyIntObject对象,而是一个PyLongObject了,如图1所示:

在PyInt_Type中的int_doc域中维护着PyIntObject的文档信息,你可以在Python的交互环境下通过下列命令看到这段文档,如图2所示:

[python.h]
/* Define macros for inline documentation. */
#define PyDoc_VAR(name) static char name[]
#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)
#ifdef WITH_DOC_STRINGS
#define PyDoc_STR(str) str
#else
#define PyDoc_STR(str) ""
#endif
[intobject.c]
PyDoc_STRVAR(int_doc,
"int(x[, base]) -> integer\n\
\n\
Convert a string or number to an integer, if possible. A floating point\n\
argument will be truncated towards zero (this does not include a string\n\
representation of a floating point number!) When converting a string, use\n\
the optional base. It is an error to supply a base when converting a\n\
non-string. If the argument is outside the integer range a long object\n\
will be returned instead.");
