Python为啥一切皆对象

282 阅读8分钟

我们都知道Python是一门面向对象语言,不论整数、字符串、类型、函数都是一个对象。换句话说,面向对象理论中的“类”和“对象”这两个概念在python中都是通过对象来实现的。这是python最大的特点。

一切皆对象

一个整数是一个对象,一个字符串也是一个对象

a = 1
b = 'abc'

通过class关键字定义的类也是一个对象:

  • 类型对象 比如整数类型
  • 实例对象 比如通过整数类型实例化得到一个整数对象
int('123')

类型、对象关系

a = 1

a是一个实例对象,它的类型是整数类型

整数类型的类型是啥呢?

type(int)  # <class 'type'>

输出可以看到,整数类型的类型还是一种类型。不过这个类型比较特殊,它的实例对象还是类型对象

python中还有一个特殊类型object,所有其它类型都继承与object

issubclass(intobject# True

那么object类型是啥呢?type的类型又是啥呢?

type(object# <class 'type'>
type(type# <class 'type'>

可以看出type是所有类型的类型,包括它自己。

object是所有其它类型的基类,那type的基类是啥呢?

issubclass(typeobject# True
type.__base__ # <class 'object'>

type的类型是它自己,基类是object,object的类型是type,那object的基类是啥呢?我们试着输出

object.__base__ # 没有任何输出

这表明object没有基类,它已经是“终极boss”了。

总结一句话:所有类型的基类为object,所有类型的类型都是type

如何理解对象?

我们都知道,Python的底层实现为C,但C并不是一门面向对象的语言,那它的对象机制是如何实现的呢?

《Python源码剖析》中这样写道,对于人的思维来说,对象是一个比较形象的概念,而对于计算机来说,对象却是一个抽象的概念。它不能理解是一个整数还是一个字符串,它知道的一切都是字节。通常的说法,对象是数据以及基于这些数据的操作的集合。在计算机中,一个对象实际上就是一片被分配的内存空间,这些内存可能是连续的,也可能是离散的,不重要,重要的是这片内存在更高的层次上可以作为一个整体来思考,这个整体就是一个对象。

那么在Python中,对象就是为C中的结构体在堆上申请的一块内存。一般,对象是不能被静态初始化的,并且也不能在栈空间上生存。但是,类型对象例外。Python中,所有的类型对象(比如int、str、list等)都是在程序运行之前就被静态地初始化了,而不是在程序运行时动态生成。这意味着在程序执行期间,类型对象的内部结构和属性是固定的,不能被修改。

那可变长度数据的对象是怎么做的呢?其实只是在对象内维护一个指向一块可变大小的内存区域的指针。

定长对象与变长对象

Python一个对象有多大呢?我们借助标准库sys模块的getsizeof函数查看对象大小

整数对象

import sys
sys.getsizeof(1# 28
sys.getsizeof(10000000000000000# 32
sys.getsizeof(10000000000000000000000000000000000000# 44

可以看到整数对象的大小跟数值有关系,大小不固定,为变长对象。

字符串对象

import sys
sys.getsizeof(''# 49
sys.getsizeof('a'# 50
sys.getsizeof('ab'# 51

可以看到字符串对象也是变长对象。

浮点数对象

import sys
sys.getsizeof(1.0# 24
sys.getsizeof(1000000000000000000000000000000000000000.0# 24

可以看出浮点数对象是定长对象

可变对象与不可变对象

可变对象在对象创建后,值可以更改;不可变对象在对象创建后的整个生命周期,值都不能修改。

整数对象是可变对象or不可变对象?

a = 1
id(a) # 4305443568
a += 1
id(a) # 4305443600

可以看出,整数类型是不可变类型,整数对象是不可变对象。修改整数对象时,将创建一个新对象,变量名与新对象进行绑定,旧对象无其它引用将被释放。

小知识

如何理解变量名与对象绑定呢?

a = 1
id(a) # 4305443568
b = a
id(b) # 4305443568

可变对象与不可变对象_00.png

变量只是一个名字,变量赋值,就是让对象与名字绑在一起。

所以,在Python内部,变量只是一个名字,保存指向实际对象的指针,赋值只拷贝指针,并不拷贝指针背后的对象。

我们看一个可变对象,比如:列表

l = [1]
id(l) # 4308872448
l.append(2)
id(l) # 4308872448

PyObject-对象机制的基石

上文提到在Python中,对象就是为C中的结构体在堆上申请的一块内存,究竟内部长啥样,只能在源码中找到答案了。

PyObject 结构体定义于头文件 object.h ,代码如下:

Include/pytypedefs.h

typedef struct _object PyObject;

定义了一个名为PyObject的结构体类型,但是该结构体的具体实现并没有在这里给出

Include/object.h

struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
};
  • _PyObject_HEAD_EXTRA 宏
  • ob_refcnt 引用计数
  • ob_type 类型指针

引用计数

对象被其它地方引用时加一,引用解除时减一;引用为0时,便可将对象回收

类型指针

指向对象的类型对象,类型对象描述实例对象的数据及行为

_PyObject_HEAD_EXTRA 宏

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

#define _PyObject_EXTRA_INIT _Py_NULL, _Py_NULL,

#else
#  define _PyObject_HEAD_EXTRA
#  define _PyObject_EXTRA_INIT
#endif

如果定义了Py_TRACE_REFS,则使用_PyObject_HEAD_EXTRA宏将两个指针(_ob_next和_ob_prev)添加到每个堆分配的Python对象的开头。这些指针用于创建所有活动堆对象的双链接列表。一般不启用。如果Py_TRACE_REFS没有定义,那么_PyObject_HEAD_EXTRA宏没有作用

那变长对象的结构体是啥样呢?

变长对象,就是在PyObject基础上加入长度信息,就是PyVarObject:

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

可以看到比PyObject只多了一个字段ob_size,用于记录元素个数。

具体对象,需要包含头部PyObjectPyVarObject 。 为此,头文件准备了两个宏定义,方便其他对象使用:

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

我们看看不变长对象,浮点对象

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

可以看到,在PyObject头部基础上,多了一个双精度浮点数

再看看变长对象,比如列表

typedef struct {
    PyObject_VAR_HEAD

    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

可以看到变长对象,需要在PyVarObject头部基础上,用一个动态数组实现,数组存储列表包含的对象,即PyObject指针。

再讲一下初始化对象头部的宏定义。

PyObject_HEAD_INIT 一般用于定长对象

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

将ob_refcnt设置为1并将对象类型ob_type设置成给定类型

PyVarObject_HEAD_INIT 一般用于变长对象

#define PyVarObject_HEAD_INIT(type, size)       \
    { PyObject_HEAD_INIT(type) size },

在PyObject_HEAD_INIT基础上设置ob_size。

PyTypeObject-类型的基石

在看PyObject源码时,

结构体中有对象指针PyTypeObject *ob_type;,它指向的类型对象决定了这个对象是啥类型。该结构体中还包含大量元信息,包括创建对象需要分配多少内存,对象支持哪些操作等。

Include/pytypedefs.h

typedef struct _typeobject PyTypeObject;

Include/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;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    // ......
   PyTypeObject *tp_base;
    // ......
};

包含PyVarObject头部,可见类型对象PyTypeObject是一个变长对象。

看代码,包含信息很多,主要看一下

  • tp_name 类型名
  • tp_basicsize, tp_itemsize 创建该类型对象时分配的空间大小信息
  • 该类型支持的相关操作信息 tp_getattr等函数指针
  • 类型的继承信息,例如 tp_base 字段指向基类对象

对象的类型由对象指向的类型对象决定,那类型对象的类型由谁决定呢?

type(1) # <class 'int'>
type(int) # <class 'type'>

int的类型为啥是type呢?PyType_Type

类型的类型

源码:Objects/typeobject.c

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

PyType_Type在类型机制中很重要,内建类型和用户自定义类对应的PyTypeObject对象都是通过PyType_Type创建的。它是所有类型的类型,称为元类型

源码中可以看到,tp_name的值为“type”,type的类型为type

type.__class__ # <class 'type'>

接下来我们看看类型对象PyType_Type之间的关系,以PyFloat_Type为例

源码:Objects/floatobject.c

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

前面提到PyVarObject_HEAD_INIT用来初始化ob_refcntob_type 以及 ob_size 三个字段。tp_name 字段初始化成类型名称 float 。ob_type指针指向PyType_Type

下图是对象运行时的图像表现

可变对象与不可变对象_01.png

我们知道,PyObject是所有类的基类,我们看看实体是如何实现的?上面提到 tp_base 字段指向基类对象,我们看到PyFloat_Type源码中,tp_base的值为0,并没有初始化,那究竟在哪里初始化的呢?

我们在 Object/object.c 看到这样一段源码

PyStatus
_PyTypes_InitTypes(PyInterpreterState *interp)
{
    if (!_Py_IsMainInterpreter(interp)) {
        return _PyStatus_OK();
    }

    // All other static types (unless initialized elsewhere)
    for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
        PyTypeObject *type = static_types[i];
        if (PyType_Ready(type) < 0) {
            return _PyStatus_ERR("Can't initialize types");
        }
        if (type == &PyType_Type) {
            // Sanitify checks of the two most important types
            assert(PyBaseObject_Type.tp_base == NULL);
            assert(PyType_Type.tp_base == &PyBaseObject_Type);
        }
    }

    return _PyStatus_OK();
}

可以看到,这里将tp_base字段初始化成PyBaseObject_Type,这就是PyObject的实体

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

可以看到,ob_type字段指向PyType_Type,这与我们开始输出的结果是一致的

object.__class__ # <class 'type'>

_PyTypes_InitTypes源码中看到,*assert(PyBaseObject_Type.tp_base == NULL);*不设置PyBaseObject_Type.tp_base ,这与我们之前的输出结果也是一致的,object没有基类,它已经是“终极boss”了

object.__base__ # 没有任何输出

现在我们知道了Python对象体系中的所有实体基关系,上面的图进行完善,得到

可变对象与不可变对象_02.png

想要第一时间看到最新文章,可以关注公众号:郝同学的测开日记