在python界有一句话: “python一切都是对象”

int, string, dict, set, class, function, module, file等都是对象

我们知道, 在面向对象程序设计中: 对象是一个实体, 类是实体的抽象特点的定义, 包括属性和方法, 通过类实例后得到对象

对于python对象而言, 有三个问题绕不过去:

  1. 什么是实例化python对象的类
  2. python类定义了哪些属性和方法
  3. 怎么实例化python类, 实例化的过程中, 初始化了哪些属性

搞清楚这三个问题, 才能理解python中一切是对象这句话。

先说上面三个问题的答案:

  1. python的类就是C语言中定义的结构体
  2. 结构体中定义了类的属性和和方法
  3. 实例化C语言的结构体即可得到python对象

首先, 我们看一个python最基础的结构体定义:

typedef struct _typeobject {
    PyObject_VAR_HEAD   /* 公共头 */
    const char *tp_name; /* 打印 "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* 定义对象的大小和对象的个数, 用于分配内存大小 */

    /* 需要实现的标准操作 */
    destructor tp_dealloc;
    printfunc tp_print;  // 定义了打印对象的方法
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;  // 定义类似于数学操作集合
    PySequenceMethods *tp_as_sequence; // 定义类似于list的操作集合
    PyMappingMethods *tp_as_mapping; // 定义了类似于map操作集合

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash; // 定义了对象的hash操作
    ternaryfunc tp_call; // 定义call(对象)操作
    reprfunc tp_str; // 定义了str(对象)的操作
    getattrofunc tp_getattro; // 定义了getattr的操作
    setattrofunc tp_setattro; // 定义了setattr的操作

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;  // 定义了__init__操作
    allocfunc tp_alloc;
    newfunc tp_new; //  定义了__new__操作
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases; // 保存所有的父类
    PyObject *tp_mro;  // 定义了查找父类的规则
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;

} PyTypeObject;

实例化PyTypeObject, 我们就可以得到第一个python对象,在实例化这个机构体之前,我们还需要需要知道PyObject_VAR_HEAD 头的结构体信息

# PyObject_VAR_HEAD是一个宏定义
#define PyObject_VAR_HEAD               
    PyObject_HEAD                       
    Py_ssize_t ob_size;  # 多了一个ob_size

# define PyObject_HEAD 
    int ob_refcnt;  # 和引用计算有关
    struct _typeobject *ob_type; # 敲黑板_typeobject就是PyTypeObject结构体标签, 也就是说ob_type这个指针必须指向PyTypeObject

PyObject_VAR_HEAD 告知我们三点:

  1. PyObject_VAR_HEAD比PyObject_HEAD 多一个属性 ob_size, 它表示对象的大小, 这里涉及到python对象概念, 定长对象和非定长对象,所谓定长就是长度是固定的, 例如整数, 非定长是长度不固定, 例如字符串, python还有一个概念:可变和不可变对象
  2. 每个对象都有ob_refcnt属性,这个是对象的引用计算,用于内存优化管理, 使用sys.getrefcount可以获取对象的引用计数
  3. 每个对象都有ob_type属性, 并且这个指针指向一个PyTypeObject, 这个属性有什么用,如何初始化复制

ob_type 是一个至关重要的属性,理解ob_type对理解python核心机制有非常大的帮助

我们先从面向对象的角度,理解类和对象的关系

In [14]: class A(object): pass
In [15]: a = A()

从面向对象的角度来讲, A是类, a是对象 ,我们可以说: a对象是A类的实例,即 a is_instance_of A 使用python语言描述isinstance(a, A) = True

这里要非常注意了,如果某个python对象(假设是b)的ob_type是另一个python对象(假设是B), 那么可以说B对象实例化后得到b对象

使用type函数或者class属性可以获取到一个对象的类

In [16]: type(a)
Out[16]: __main__.A

In [17]: a.__class__
Out[17]: __main__.A  # 这里的输出就是tp_name的赋值

所以 对象的ob_type属性的意义是决定了该对象的”类”,前文提到,python中的一切都是对象,所以A是对象,a也是对象

我们可以说A对象通过实例化得到了a对象, 这里我们又推导出python对象和面向对象开发的很大的不同:

实例化一个python对象可以得到python 另一个对象, ob_type保存的就是某个对象的"类"

理解这一点至关重要!!!!!

到目前为止,我们讲清楚了python对象的机构的头部信息,除了头部结构体信息以外,我们还需要关注这个结构体的其它信息

常见的有tp_name,tp_basicsize, tp_itemsize, 其中 tp_basicsizetp_itemsize 决定了分配内存的大小

接下来就是一些方法的初始化, 我们常用对象的方法就是在这里初始化, 例如 print, new, init, string操作集合, list操作集合等

现在我们就来初始化我们的第一个python对象

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0) 的父类也是type
    "type", // 初始化tp_name, type(int) 输出type就是这里定义的
    sizeof(PyHeadTypeObject), // 初始化tp_basicsize
    sizeof(PyMemBerDef), // 初始化tp_itemsize
    ...省略其他函数和属性的初始化...
}

到目前为止, 我们学会了怎么初始化PyTypeObject, 同时恭喜你,你得到大名鼎鼎的PyType_Type(type)对象, 下面统一描述为type对象

type对象是所有内置(int, string, list, object,file)对象之母,也就是说内置对象的ob_type=type, 也就是说内置对象 `is_instance_of type对象

In [23]: type(str)
Out[23]: type

In [24]: type(int)
Out[24]: type

In [25]: type(object)
Out[25]: type

In [30]: type(dict)
Out[30]: type

In [26]: type(type)
Out[26]: type

In [28]: type.__class__  # 注意
Out[28]: type

In [31]: isinstance(int,type)  # 注意
Out[31]: True

这里需要特别关注的是type对象的ob_type指向的是自己, 说明了type是由type类实例化得到的

现在我们知道通过type对象可以实例化出PyInt_Type(int对象), PyString_Type(str对象), PyDict_Type(dict对象)等内置对象

以PyInt_Type的生成过程为例,详细说明内置对象的生成过程:

PyTypeObject PyInt_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) // 初始化头部, ob_type赋值为type对象
    "int", // 初始化tp_name
    sizeof(PyIntObject), // 初始化tp_basicsize, 这里用到了PyIntObject这个结构体, 我们接下来会聊到
    0 // 初始化tp_itemsize, 因为int对象是固定长度对象, 没有item属性, 所以是0

    ...省略其他函数和属性的初始化...
}

有了int对象后, 我们就可以通过这个对象实例化出来一个整数

In [32]: a = int(1)  # 使用int对象,得到一个具体的int整数

In [33]: a
Out[33]: 1

In [34]: a = 1  # 更简洁的方法初始化一个int对象

通过int对象很自然的就可以得到一个整数对象,回顾上文,我们提到在python中,所有的数据都是对象,包括整数对象

那整数对象的结构体是什么呢,请接着看,整数对象的结构体如下:

typedef struct {
    PyObject_HEAD # PyObject_HEAD是一个宏定义
    long ob_ival; # 整数的值
} PyIntObject  # PyIntObject结构体就是整数对象的结构体

可以使用python的内置函数PyInt_FromLong初始化得到一个具体的整数

PyInt_FromLong(long ival)
{
    ...准备工作...

    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);  # 分配内存

    PyObject_INIT(v, &PyInt_Type);  # 注意啦,整数对象的ob_type是int对象
    v->ob_ival = ival;
    ...其他操作...
    return (PyObject *) v;
}

到这里, 我们基本可以总结:

  1. 初始化PyTypeObject结构体, ob_type=PyType_Type可以得到PyType_Type对象
  2. 初始化PyTypeObject结构体(传递的参数和创建type对象不同), ob_type=PyType_Type 实例化后可以得到PyInt_Type
  3. 初始化PyIntObject结构体, ob_type=PyInt_Type可以得到整数对象

In [35]: a = 1

In [36]: type(a)  # 整数对象的ob_type是int
Out[36]: int

到最后, 我们可以负责的说, 初始化python对象有两个核心工作:

  1. 需要知道对象的结构体
  2. 需要指定对象的ob_type

稍微提及一下一个有趣的概念是: a.ob_type = A, 那么A对象可以称为a对象的元类(metaClass)

理解了ob_type, 相当于理解了整个python对象创建的逻辑

初始化PyStringObject对象

PyStringObject的ob_type对象是PyString_Type, 也就是说PyStringObject是PyString_Type实例化得到的

PyTypeObject PyString_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0) // 初始化头
    "str",  // tp_name
    PyStringObject_SIZE, // tp_basicsize
    sizeof(char), // tp_itemsize
    string_dealloc,
    (printfunc)string_print,
    0,
    0,
    0,
     string_repr,
    &string_as_number,   // 初始化了数字操作集合
    &string_as_sequence, // 初始化了list操作集合
    &string_as_mapping, // 初始化了map操作集合
    (hashfunc)string_hash,

PyStringObject结构如下:

typedef struct {
    PyObject_VAR_HEAD // 头部信息
    long ob_shash; // 是否hash过了
    int ob_sstate; // 优化相关的
    char ob_sval[1]; // 指向维护实际的字符串内容的内存地址
} PyStringObject;

于是 生成一个PyStringObject对象的方法如下:

PyString_FromStringAndSize (const char *str, Py_ssize_t size)
{

    op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); // 初始化了PyStringObject
    PyObject_INIT_VAR(op, &PyString_Type, size); // 使用了PyString_Type, 注意ob_size的赋值
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
}

类似的还有dict, set, map等结构都是类似于这种方式创建的

初始化指针函数

在PyTypeObject结构体中, 我们可以看到很多函数需要初始化, 这些函数初始化之后, 就可以操作结构体中的数据, PyTypeObject的函数大概分为这几类:

  1. 初始化操作 [tp_new, tp_init]
  2. 类型化操作函数[tp_as_number, tp_as_sequence, tp_as_mapping]
  3. 常规操作函数[tp_hash, tp_call, tp_compare, tp_iter, tp_iternext, tp_print]
  4. Python Attribute descriptor [tp_dict,tp_descr_get, tp_descr_set ]
  5. 内存管理函数[tp_allocs, tp_frees, tp_maxalloc]

在python内置类型中, int类型初始化tp_as_number, string类型初始化了tp_as_number, tp_as_sequence, tp_as_mapping

所有, 猛然惊醒, 如果我们想要int对象也具有list对象的某些行为, 只需要实现tp_as_sequence就可以

属性和方法的查找

上文提到,整数对象的结构体是:

typedef struct {
    PyObject_HEAD # PyObject_HEAD是一个宏定义
    long ob_ival; # 整数的值
} PyIntObject  # PyIntObject结构体就是整数对象的结构体

这个结构体并没有定义整数的相关操作方法(+, -), 那整数对象是如何拥有和整数相关的方法的呢,同理string对象也有类似的疑问

答案就在ob_type中,我们知道整数对象的ob_type是int对象,int对象初始化了和整数相关的操作方法,当我们操作整数方法的时候,如果找不到,则从int对象里面去查找

这类似于面向对象中继承关系。

对象的优化

试想一下,在python项目中,需要创建int对象,string对象,dict对象,同时,还可能创建很多整数对象

python是怎么优化这么多整数对象的呢?答案是池化技术。