06月24, 2020

3. Object 的基本结构

Python 对象

Python 是高度面向对象的动态语言,从 Python 3 开始,包括所有基本类型在内,都是对象。CPython 是由非常底层的 C 语言实现的,它即不是动态语言,也不是支持面向对象,那么 Python 的对象又是如何实现的呢?我们可以从 /Include/Object.h 中窥探在 C 代码中,如何在内存中表示 Python 的对象,并在此基础上实现 Python 的动态性。

PyObject

在 C 语言中,需要多少内存,程序员就需要手动调用 malloc 族函数,申请准确大小的内存使用,当然,最后也需要程序员自己在用完之后释放内存。Python 作为一种高级语言,可以自行管理内存,在对象不再被使用的时候,通过一些自动机制释放掉。由于将内存管理工作委托给语言进行,创建的内存对象就需要额外的一块区域保存这些附加信息。

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   PyObject ob_base;

PyObject_HEAD 会出现在所有 Python 对象的头部,只要是Python 中的对象,就一定以这个 PyObject 结构开始,其定义如下:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

第一部分 _PyObject_HEAD_EXTRA 是一块调试信息,非 debug 情况下是一个空的宏,所以一个 Python Object 的头部看起来是这样:

image.png

其中,ob_refcnt 表示当前对象的 “引用计数”,当一个对象被创建的时候,ob_refcnt 的值被设置为 1,因为对象被创建出来,必然会被某一个变量关联引用;之后每当发生一次显式或者隐式变量赋值(不是复制)都会增加这个对象的引用计数,当一个变量不再指向这个变量,它的引用计数就会减少 1;当对象的引用计数下降到 0,就意味着这个对象不再被需要,就可以被释放了。

引用计数与 Python 的 GC 密切相关,在源码中会频繁更新对象的引用计数,需要特别注意,错误操作引用计数会造成内存泄漏或者内存错误。从 ob_refcnt 的角度来看,对象的内存区域可以看作是一个以引用计数开头的连续内存。

第二部分 ob_type 是一个对象指针,用于表示当前对象的类型,在 Python 中使用 type 命令看到的就是对象的类型对象。Python 的面向对象类型非常有意思,其类型也是一个对象,也就是说在 Python 中 “类” 也是用一个全局对象表示的,对象和类的界限发生了模糊。这看起来也许有点怪异,实际上这为 Python 提供了一种非常自由动态能力,在后面我们再详细分析,这里暂时记住,第二部分是一个表示当前对象类型的指针。

每当一个对象被创建的时候,会对这些头部信息进行初始化

#define PyObject_HEAD_INIT(type)        \
    { _PyObject_EXTRA_INIT              \
    1, type },

_PyObject_EXTRA_INIT 是两个 NULL,暂时不关心它们,比较重要的是后面的 1 ,对比前面的代码,实际上这是将对象的引用计数设置为 1,表示这个对象被创建出来,立刻会被某个变量引用。

PyVarObject

Python 中存在多种类型,其中用户最常用的是一些储存变量的对象,这类对象会利用 PyObject 头部信息以后的空间,储存一些信息,可能是一个数字、一个字符串、甚至一个用户自定义的类型,显然将这么多种各异的数据统一保存在相同大小的内存中是不可能的,上图中 object body 部分的大小必然是不同的。 PyVarObject 是在 PyObject 的基础上增加数据大小的对象。

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

image.png

可以认为 PyVarObject 是 PyObject 的一个子类,PyVarObject 指针可以安全的转换为 PyObject 指针。

/* Cast argument to PyObject* type. */
#define _PyObject_CAST(op) ((PyObject*)(op))

ob_size 表示 object body 中可变长度的内容大小,这样 Python 就可以用通用的 PyObject 指针来引用所有对象了, 是 Python 实现面向对象的关键之一。Int、String 等等都属于该类型。

明天我们将从最基本的 Int 类型入手,了解 Python 的对象实现。

本文链接:http://www.thinkinpython.com/post/deep_py_vm_3.html

-- EOF --