Python对象初探

在Python中,一切皆对象。1989年,Guido在圣诞节正式发布了Python。Python是用C写的。所谓的对象在计算机底层其实只是一些内存的集合,这些内存可以是离散的也可以是连续的。为了编程方便,我们在写业务的时候往往不考虑底层的实现,直接把可以看作整体的内存抽象成对象。一般来说,对象是不能被静态初始化的,并且也不能在栈空间上生存。唯一的例外就是类型对象。Python中所有的内建类型对象都是被静态初始化的。

在Python中一个对象一旦被创建,它在内存中的大小就是不变的,也就是说,可变长数据都是通过指针来实现的。这样做可以确保Python对象不会印象内存周围的对象。

所有的Python对象的基类都是object。object在C源码中对应的就是PyObject。

[object.h]
typedef stryct _object {
    PyObject_HEAD
} PyObject;

#define PyObject_HEAD       \
    _PyObject_HEAD_EXTRA    \
    int ob_refcnt;          \
    struct _typeobject *ob_type;

其实结构很简单
typedef struct _object {
    int ob_refcnt;
    struct _typeobject *ob_type;
}
这个结构体是Python对象机制的核心。ob_refcnt主要是为了实现垃圾回收机制。ob_type则指向一个类型对象,表明这个对象的类型。也就是说,Python中最简单的一个对象必须包含两个信息:引用计数和类型信息。因为所有的Python对象都继承自PyObject,所以每个对象都包含了PyObject_HEAD这个宏,而且这个宏中的内容会出现在每一个Python对象所占有的内存最开始的字节中。这样以后无论是什么Python对象我们都能用PyObject * 来引用了。
typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;
这是Python中整数对应的C结构。ob_ival表示整数值。对于字符串这样的变长类型。python中做了如下处理。
#define PyObject_VAR_HEAD       \
    PyObject_HEAD       \
    int ob_size;

typedef struct {
    PyObject_VAR_HEAD
} PyVarObject;
PyVarObject表示Python中的变长类型,其中ob_size表示包含内部对象的数量。 想要创建一个对象,首先要做的就是给这个对象分配内存。至于分配多少内存,我们没有把这个值记录在对象中。因为每个类型需要的内存是固定的,所以我们将每个对象占用的内存大小存放在了类型对象(PyTypeObjec)中。
typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name;                  /* 类型名称 For printing, in format "." */
    int tp_basicsize, tp_itemsize;  /* 内存大小 for allocation*/
    destructor tp_dealloc;          /* 内存回收函数 */
    printfunc tp_print;             /* print调用的方法 */
    ...
    hashfunc tp_hash;               /* 计算哈希值 */
    ternaryfunc tp_call;            /* 不知道是干嘛用的,在后面的章节看到过。好像tp_call不为空,那么这个类型就是可调用的 */
    ...
} PyTypeObject;

有两种方法可以在Python中创建一个整数,一个是通过CAPI,一个是通过PyInt_Type对象来创建。CAPI是个很高大上的东西,从学Python到现在还从来没真正用过,真是惭愧惭愧呀,看来离Pythoner的距离还是相当遥远啊。这里给两个例子就跳过了。

  • PyObject* intObj = PyObject_New(PyObject, &PyInt_Type)。
  • PyObject* intObj = PyInt_FromLong(10)。

但是CAPI只能创建内建对象,对于用户自定义的类型,则只能通过类型对象来创建。首先通过类型对象PyIntType找到tp_new这个属性,如果这个属性为NULL,则寻找基类(PyObject)的tp_new。tp_new方法会找到PyInt_Type中记录的tp_basicsize。从而完成内存申请。PyIntObject的tp_basicsize的值为sizeof(PyIntObject)。在完成内存申请之后会调用PyInt_Type中的tp_init,完成一些初始化工作。对应到C++,tp_new可以看作new关键字,tp_init可以看作是构造函数。

PythonTypeObject定义了很多函数指针,这些指针都是这个对象的一些方法。在这些操作方法中,有三组非常重要的操作族。在PyTypeObject中,它们是tp_as_number, tp_as_sequence, tp_as_mapping。他们分别指向PyNumberMethods, PySequenceMethods和PyMappingMethods函数。

typedef PyObject * (*binaryfunc)(PyObject *, PyObject *);

typedef struct {
    binaryfunc nb_add;
    binaryfunc nb_syvtract;
} PyNumberMethods;
在object.h这个文件中定义了这三个族的操作都有哪些(不小心看到的)。当然只要重写这些操作对应的包装器就能使一个对象拥有多个族的操作。 关于类型的类型(PyType_Type)这个东西我想我还是比较熟悉的。
PyTypeObject PyType_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,                          /* ob_size */
    "type",                     /* tp_name */
    sizeof(PyHeapTypeObject),   /* tp_basicsize */
    sizeof(PyMemberDef),        /* tp_itemsize */
}
type对象是普通类型对象的类型,也叫metaclass,当然你也可以自定义一个类型的metaclass。 在Python内部有一个十分常用的范型指针PyObject *。在Python内部各个函数之间传递的都是PyObject*,也正是由于PyObject*,Python实现了多态机制。 Python和C#一样,都是采用引用计数来实现垃圾回收机制的。不过有一个有意思的地方就是Python的类型对象是不遵守垃圾回收机制的,也就是说一个类型对象被创建,就永远不会析构。
#define _Py_NewReference(op)    ((op)->ob_refcnt = 1)       // 一个对象创建时,将这个对象初始化成引用为1
#define _Py_Deakkoc(op)     ((*(op)->ob_type->tp_dealloc)((PyObject *)(op)))    // 析钩函数
#define PyINCREF(op)    ((op)->ob_refcnt++)         // 增加引用计数
#define Py_DECREF(op)   \                           // 减少引用计数
    if (--(op)->ob_refcnt != 0) \
        ;
    else
        _Py_Dealloc((PyObject *)(op))

#define Py_XINCREF(op)  if  ((op) == NULL) ;    else Py_INCREF(op)  // 增加引用
#define Py_XDECREF(op)  if  ((op) == NULL) ;    else Py_DECREF(op)  // 减少引用

还有一点需要注意的是,这里析构函数不一定会通过free释放内存,大部分时候是将内存换给内存对象池。因为频繁地申请和释放内存会导致Python效率低下。

第一章的最后一部分是介绍Robert Chen将Python对象系统分成五大类。

  • Fundamental对象:类型对象
  • Numberic对象:数值对象
  • Sequence对象:容纳其他对象的序列集合对象
  • Mapping对象:类似于C++中map的关联对象
  • Internal对象:Python虚拟机在运行时内部使用的对象

好了今天就到这里吧,第一章讲的内容大部分也已经清晰了。这几天比较累,还是早点休息吧。

对了,端端说很不甘心,我也很不敢心,好好加油吧。