2022年 11月 8日

python3源码剖析新版_《Python源码剖析》

1. Python内建对象

对象是数据以及基于这些数据的操作的集合,在Python中,对象就是为C中的结构体在堆上申请的一块内存。

在Python中,一个对象一旦被创建,它在内存中的大小就是不变的了,那些需要容纳可变长度数据的对象只能在对象内维护一个指向一块可变大小的内存区域的指针。

1.1.1 对象机制的基石 — PyObject

[object.h]

typedef struct _object{

int ob_refcnt; /* 引用计数器 */

struct _typeobject *ob_type; /* _typeobject这个结构体用来指定一个对象类型的类型对象 */

} PyObject

在Python中,对象机制的核心非常简单,一个是引用计数,一个是类型信息

在PyObject中定义了每一个Python对象都必须有的内容,除了PyObject以外,还应该有一些额外的内存,放置其他信息。

1.1.2 定长对象和变长对象

Python整数对象PyIntObject

typedef struct{

PyObject_HEAD

long ob_ival;

} PyIntObject

整数对象的特殊信息是一个C中的整形变量,无论这个整数对象的值有多大,都可以保存在这个整形变量中。

变长对象PyVarObject

#define PyObject_VAR_HEAD PyObject_HEAD int ob_size; /* 容器中元素的数量 */

typedef struct{

PyObject_VAR_HEAD

} PyVarObject;

我们把整形对象这样不包含可变长度数据的对象称为“定长对象”,而字符串对象这样包含可变长度数据的对象称为“变长对象”,它们的区别在于定长对象的不同对象占用内存大小是一样的,而变长对象的不同对象占用内存可能不一样。

PyVarObject只是对PyObject的一个扩展而已,在Python内部,每一个对象都拥有相同的对象头部。

1.2 类型对象

创建对象的时候,必须知道要申请多大的空间,所需空间大小是对象的一种元信息,这样的元信息与对象所属类型密切相关。我们考察一下类型对象_typeobject:

typedef struct _typeobject {

PyObject_VAR_HEAD

char *tp_name; /* tp_name 类型名 */

int tp_basicsize, tp_itemsize; /* 创建该类型对象时分配内存空间的大小 */

/* 与对象关联的操作信息 */

destructor tp_dealloc;

printfunc tp_print;

/* 更多操作信息 */

hashfunc tp_hash;

ternaryfunc tp_call;

} PyTypeObject;

一个PyTypeObject就是Python中面向对象理论中“类”这个概念的实现

1.2.1 对象的创建

Python创建对象的方法:

通过Python C API来创建

通过类型对象 PyInt_Type来创建

Python对外提供了C API让用户可以从C环境中与Python交互,Python的C API分为两类:

范型API,或称为AOL

另一类称为COL

从PyInt_Type创建整数对象

说明: 在Python完成运行环境的初始化后,符号‘int‘就对应着Python内部的PyInt_Type对象,object则对应着PyBaseObject_Type对象

使用init(10)创建对象时,就会调用PyInt_Type对象中的tp_new操作

假如这一个tp_new操作不存在,为NULL,那么就到tp_base指定的基类里去找tp_new操作

在Python2.2以后的新式类中,所有的类都是以object为基类的,所以最终一定可以找到一个不为NULL的tp_new

tp_new指向了object_new,object_new会访问PyInt_Type中记录的tp_basicsize信息,得到需要申请的内存大小,然后完成申请内存的操作。我们前面说了,“在Python中,对象就是为C中的结构体在堆上申请的一块内存”,所以内存申请完成就代表对象已经创建(但还未初始化)

tp_new创建完对象后,流程转向PyInt_Type的tp_init,完成初始化工作

当我们用int(10)创建整数对象10时,首先PyInt_Type中的tp_new会被调用,如果这个tp_new为NULL,那么会到tp_base指定的基类中去寻找tp_new操作

1.2.2 对象的行为

在PyTypeObject中定义了大量的函数指针,这些函数都最终会指向某个函数,或者指向NULL,这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现出的行为。

比如PyTypeObject中的tp_hash是一个函数指针(hashfunc)类型的变量,tp_hash指明对于该类型的对象如何生成其hash值。

在PyTypeObject中指定的不同的操作信息也正是一种对象区别于另一种对象的关键所在。

在这些操作族中,有三组非常重要:

tp_as_number

指向PyNumberMethods函数族,该族定义了一个数值对象应该支持的操作,典型对象如int,tp_as_number.nb_add指定了

对该对象进行加法操作时的具体行为。

tp_as_sequence

指向PySequenceMethods函数族,该族定义了作为一个序列对象应该支持的操作,典型对象list.

tp_as_mapping

指向PyMappingMethods函数族,该族定义了作为一个关联对象应该支持的操作,典型对象dict。

对一种类型来说,它完全可以同时定义三个函数族中的所有操作,只要定义相应的special_method

1.2.3 类型的类型

在Python中类型其实也是一个对象,类型的类对应Python内部的PyType_Type,它是所有class的class,在Python中称为metaclass,元类。

此处留疑问??????????????

[object.h]

#ifdef Py_TRACE_REFS

#define _PyObject_EXTRA_INIT 0,0,

#else

#define _PyObject_EXTRA_INIT

#endif

#define PyObject_HEAD_INIT(type) _PyObject_EXTRA_INIT 1, type

番外篇: #define学习

\反斜杠把该定义延续到下一行,编译器将\分成的多个物理行转换为一个逻辑行,并删除\符号

#define预处理器指令以#号作为一行的开始,指令可以出现在源文件的任何地方,其定义从指令出现的地方到该文件末尾有效。

每行#define(逻辑行)都由3部分组成:

第1部分是#define指令本身

第2部分是选定的缩写,也称为宏,有些宏代表值,这些宏被称为类对象宏

第3部分称为替换列表或替换体

从宏变成最终替换文本的过程称为宏展开

在#define中使用参数可以创建外形和作用与函数类似的类函数宏