1.2 Python3 对象深入分析

对象的基石,PyObject

上一章节探讨了python一切皆对象的由来。在python中,对象可分为类型对象和实例对象,而根据不同的对象特性,又可细分为可变、不可变、定长、不定长等。

Python是由C语言实现的(Python虚拟机是C语言编写的,可以直接调用C语言函数。很多内建对象是由C语言编写的,模块对象保存了这些C函数的指针。当Python调用这些对象时,CALL_FUNCTION字节码会找到这些函数指针并调用它们),那么对象在Python内部是如何实现的?

在Python内部,所有对象均是由PyObject结构体表示,对象引用则是指针PyObject*。在Python源码include/object.h中,定义了PyObject结构体。(以Python3.10.0为例)

/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 */
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

以上代码定义了一个结构体_object以及结构体变量PyObject。

首先看结构体的成员_PyObject_HEAD_EXTRA宏。

#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;

#define _PyObject_EXTRA_INIT 0, 0,

#else
#  define _PyObject_HEAD_EXTRA
#  define _PyObject_EXTRA_INIT
#endif

根据注释和代码解读,可以了解当Py_TRACES_REFS有定义,则将宏定义为两个指针*_ob_next和*_ob_prev。这两个指针实现了双向链表,用于跟踪所有活跃堆对象。通常情况下不启用,这里不做深入了解。

PyObject结构体中,还包含两个成员:

  • 引用计数(ob_refcnt):对象被其他地方引用时加一,引用解除时减一,当引用计数为零,便可将对象回收(垃圾回收的主要机制之一)。
  • 类型指针(ob_type):指向对象的类型对象,类型对象描述实例对象的数据和行为。

继续往下阅读object.h代码,可以看到结构体变量PyVarObject。

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

这是Python变长对象所对应的C结构体,在原先的PyObject基础上加入了长度信息ob_size,用于记录元素的个数。

在对象中,定长与变长是二选一的属性,如果不是定长就是变长,也就是说在对象的结构体中,需要包含头部PyObject或PyvarObject。为此,头文件特地准备了两个宏定义,分别表示定长宏与不定长宏:

#define PyObject_HEAD          PyObject ob_base;
#define PyObject_VAR_HEAD      PyVarObject ob_base;

各对象结构组成可概况如下:

以大小固定的浮点对象而言,只需要在PyObject头部基础上,添加一个item即可,浮点对象的item使用一个双精度浮点数double加以实现。

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

而对于变长的列表对象而言,则需要一个PyVarObject以及动态数组。

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

其中三个关键字段含义如下:

  • ob_item,指向动态数组的指针,数组保存元素对象指针;
  • allocated,动态数组总长度,表示当前列表的容量,在Python中,如果列表容量不足时,会自动扩容,这个需要与列表实际的元素数量区分开;
  • ob_size,当前列表中的元素个数,即列表当前的长度(len(ob))。

 

类型的基石,PyTypeObject

从对象的基础结构体PyObject,可以了解所有对象的共有信息。对于内存中的任一对象,不管是何类型,肯定会存在 引用计数、类型指针 以及变长对象特有的 元素个数。

但以我们对Python对象的了解,PyObject无法解决以下问题:

  • 不同类型的对象所需内存空间不同,创建对象时从哪里得知内存信息?
  • 对于给定对象,怎么判断它支持什么操作?

事实上,这些信息称为对象的元信息,由一个独立实体保存,与对象所属类型密切相关。还记得PyObject内包含一个指针ob_type吗?该指针指向了一个类型对象,该类型对象便是PyTypeObject。

PyTypeObject的代码在cpython/object.h文件中,部分代码如下:

struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;

    // ...
    

    // Strong reference on a heap type, borrowed reference on a static type
    struct _typeobject *tp_base;
    
    // ...
};

重要字段如下:

  • PyObject_VAR_HEAD:类型对象是一个变长对象;
  • 类型名称:tp_name字段;
  • 类型的继承信息,例如tp_base字段指向基类对象;
  • 创建实例对象所需要的内存信息,即tp_basicsize和tp_itemsize字段;
  • 该类型支持的相关操作信息,即tp_getattr、tp_setattr等函数指针;

 

以浮点为例,通过is关键字判断类型对象和实例对象在内存中(id)的形态和关系:

>>> float
<class 'float'>
>>> pi = 3.14
>>> e = 2.71
>>> type(pi) is float
True
>>> type(e) is float
True

 float为浮点类型对象,系统中是唯一的,保存了所有浮点实例对象的元信息。所以实例对象pi和e的type均是float对象。

各对象的关系在内存中的形式如下:

如图可见,两个浮点实例对象都是PyFloatObject结构体,除了公共头部字段ob_refcnt和ob_type,专有字段ob_val保存了对应的数值。其中ob_type字段指向了float的类型对象(也是一个PyTyoeObject的结构体),这里保存了类型名、内存分配信息以及浮点相关操作等。Python便是依据对象类型,进而得知对象元信息。注意,这里float、pi以及e等变量只是一个指向实际对象的指针。

浮点类型对象是全局唯一的,在C语言层面上作为一个全局变量静态定义即可。浮点类型对象PyFloat_Type代码位于Object/floatobject.c文件中:

PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),
    0,
    (destructor)float_dealloc,                  /* tp_dealloc */
      
    // ...
    (reprfunc)float_repr,                       /* tp_repr */
    
    // ...
};

在PyFloat_Type结构体中,第二行初始化了ob_refcnt、ob_type以及ob_size三个字段;第三行将tp_name字段初始化成类型名称float;再往下就是各种操作的函数指针。

需要注意到第二行中ob_type指针指向PyType_Type,这也是一个静态定义的全局变量。由此可见,代表“类型的类型”即type的那个对象应该就是PyType_Type了。

 

类型的类型,PyType_Type

上一节了解了,所有对象的元类型都是type,这里的PyType_Type的就是所说的type的结构体。其代码在Object/typeobject.c中:

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */
    (destructor)type_dealloc,                   /* tp_dealloc */
    
    // ...
};

内建类型和自定义类对应的PyTypeObject对象都是通过这个PyType_Type创建的。PyType_Type在Python的类型机制中是一个非常重要的对象,是所有类型的类型,称为元类型(meta class)。

所有的类型对象,包括内建类型、自定义类还有元类,本质上都是类型对象。底层结构是类型的,内存结构均由PyTypeObject结构体定义。以自定义类型为例,调用type(PyType_Type),type将为新的自定义类分配内存(PyTypeObject结构体)并初始化相关字段;以内建对象为例,由于内建对象都是静态定义,type无须为其分配内存,但仍负责字段的初始化。

类型之基,PyBaseObject_Type

在文件Object/typeobject.c中,可以查看PyBaseObject_Type的代码:

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "object",                                   /* tp_name */
    sizeof(PyObject),                           /* tp_basicsize */
    
    // ....
};

根据第二行的代码,object的类型也是type,此外,object就是继承链的末端,没有设置具体的tp_base值。

>>> object.__class__
<class 'type'>
>>> object.__base__
>>>

至此,我们大致清楚了Python对象体系中所有实体以及关系。

 

 

 

 

 

 

 

 

 

 

posted @ 2021-11-10 22:50  bearoff  阅读(128)  评论(1)    收藏  举报