Python 源码剖析 第一章

第一部分 Python内建对象

Python 一切皆对象

怎么理解对象?

对象实际上是对一类事物的共同点的抽象, 比如int对象, string对象, 属于某个对象的事物,具有一定的共性, 这是给人类定义的, 对于计算机而言, 一切都是二进制数, 它所知道的一切都是字节, 对计算机而言, 对象是一片被分配的内存, 这内存可能是连续的,也可能是离散的, 对象的抽象表现为数据+行为

  • Python中所有的内建对象(整型类型对象, 字符串类型对象等) 都是被静态初始化

  • 对象一旦被创建,内存的大小是固定不变的

    这样避免了, 某对象内存大小改变导致其他对象地址发生变化,从而可能导致连锁反应,不同对象的大小是不同的

Python为了减少内存分配释放开销, 大量的使用对象池, 用于复用内存, 而这能实现的前提是同一种类型的对象大小固定不变

对象机制的基石(PyObject)

在python中一切皆对象, 所有的对象都是基于PyObject实现

1
2
3
4
[object.h]
typedef struct _object {
PyObject_HEAD
} PyObject;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[object.h]
#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

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
int ob_refcnt; \
struct _typeobject *ob_type;

一般Py_TRACE_REFS用于debug使用,正式的Python代码中没有定义

因此实际上每个Python对象的initial segment 为如下结构

1
2
3
4
5
[object.h]
typedef struct _object {
int ob_refcnt; // 引用计数
struct _typeobject *ob_type; // 指明类型信息
} PyObject;

ob_refcnt 用于引用计数, 用于垃圾回收,

ob_type 用于指明类型信息

Python中的一切对象都可以理解为继承了PyObject结构

定长对象与变长对象(PyVarObject)

同一个struct的不同实例,如果占用的内存大小相同, 则为定长对象, 反之则不是

对于int 类型struct, 其实例的占用内存大小是相同的, 所以他们是定长对象

对于list, str其为变长对象

str和list可以理解为, 其存储了一组对象

Python为这一类变长对象, 定义了一个通用父类PyVarObject

1
2
3
4
5
6
7
8
[object.h]
#define PyObject_VAR_HEAD \
PyObject_HEAD \
int ob_size; /* Number of items in variable part */

typedef struct {
PyObject_VAR_HEAD
} PyVarObject;

ob_size 表示其所维护的对象元素的个数

对于定长对象, 比如 PyIntObject, 其定义如下

1
2
3
4
5
[intobject.h]
typedef struct {
PyObject_HEAD
long ob_ival; // 在Python2.5源码中, int 对象值实际上是long类型
} PyIntObject;

如果ob_ival中的值超过long大小, Python会将对象升级为PyLongObject

类型对象(PyTypeObject)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[object.h]
typedef struct _typeobject {
PyObject_VAR_HEAD
char *tp_name; /* For printing, in format "<module>.<name>" */
int tp_basicsize, tp_itemsize; /* For allocation */

/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
……
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
……
} PyTypeObject;

所有的对象都具有一个类型对象, 它定义了这个对象的具有的属性和行为

在创建一个类的时候,势必要开辟一段内存, 初始化值, 等操作, 这一类型操作, 皆在_typeobject中定义

每个对象的创建方式, 初始化方式可能不同, 因此, Python利用函数指针(Function pointer)来实现, 用来实现多态

下面简单以int对象举例,用于理解类型对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[intobject.c]
// PyInt_Type 的声明
extern PyTypeObject PyInt_Type;

// 定义PyInt_Type 类型对象
PyTypeObject PyInt_Type = {
// 初始化引用计数为1, ob_type 指针指向PyType_Type, 表明其类型对象的类型是PyType_Type!!!
// PyType_Type 这个后面会提到, 实际上他就是Python中的type
PyObject_HEAD_INIT(&PyType_Type)
0,
"int",
sizeof(PyIntObject),
...
// tp_base 用于表示基类, 这边为0, 表示没有, 如果在此对象中没找到对象创建和初始化方法, Python会通过tp_base指针, 找到其基类,调用其创建(new)和初始化(init)方法
0, /* tp_base */
int_new, /* tp_new */
(freefunc)int_free, /* tp_free */
};

从上面代码中可以看出, 类型对象(PyTypeObject)是一个变长对象(struct结构中具有PyObject_VAR_HEAD),

这个类型对象主要定义了:

  • 类型名,tp_name,主要是Python内部以及调试的时候使用
  • 创建该类型对象时分配内存空间大小的信息,即tp_basicsize和tp_itemsize
  • 与该类型对象相关联的操作信息(就是诸如tp_print这样的许多的函数指针), 函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现出的行为
    • tp_as_number、tp_as_sequence、tp_as_mapping。它们分别指向PyNumberMethods、PySequenceMethods和PyMappingMethods函数族
    • tp_as_number: 定义了作为一个数值对象应该支持的操作, 比如四则运算
    • tp_as_sequence: 定义了作为一个序列对象应该支持的操作
    • tp_as_mapping: 定义了作为一个关联对象应该支持的操作
  • 类型的类型信息

类型对象是特殊的, 它们不会被垃圾回收, 增加/删除对这类对象的引用, 不会影响此类对象的引用计数

类型对象的类型(PyType_Type)

1
2
3
4
5
6
7
8
9
10
[typeobject.c]
PyTypeObject PyType_Type = {
// 需要注意到, PyType_Type对象的对象类型是它本身
PyObject_HEAD_INIT(&PyType_Type)
0, /* ob_size */
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
……
};

需要注意到, PyType_Type 实际上也是 PyTypeObject, 其他的内置类型对象(int,str,list,dict等)或者用户自定义的类型对象(class), 其对象类型都是PyType_Type!!!(比较绕, 需要注意这些类型对象的PyObject_HEAD_INIT方法就能理解)

1
2
3
4
5
6
7
8
9
10
# int 对象的类型是int
In[1]: a = 1
In[2]: a.__class__
Out[2]: int

# 自定义类的类型是type
In[3]: class pp(object):
pass
In[4]: pp.__class__
Out[4]: type

可以这么理解:

  • Python语法中的class, 在源码中就是PyTypeObject对象, 其类型是PyType_Type对象

下面图是示意int对象创建初始化的流程:

  • 调用ob_type中的方法实现对象的创建和初始化
  • ob_type 对象其也有类型(ob_type), 指向 PyType_Type 对象
  • PyType_Type 对象 的类型(ob_type) 指向其本身

垃圾回收

  • 引用计数的是Python的垃圾回收机制的一部分, 每个Python对象都带有ob_refcnt, 当ob_refcnt为0,代表该对象不可被访问, 其占用的内存可被回收再利用

  • 在Python的各种对象中,类型对象是超越引用计数规则的。类型对象“跳出三界外,不再五行中”,永远不会被析构。每一个对象中指向类型对象的指针不被视为对类型对象的引用

  • 引用计数引起的垃圾回收是在Py_DECREF(op)这里进行触发的,同时会触发后续会提到的对象池中的内存复用操作

多态的实现

  • Python 一切皆对象, 所有的对象初始内存皆具有相同的结构—->PyObject, 因此可以利用泛型指针(PyObject*)来操作Python中的一切对象, 如果要查看指针所指向对象的类型, 可以通过_typeobject * ob_type 泛型指针来找到特定的类型对象, 从而拿到对象的metadta

可以参考Print函数

1
2
3
4
void Print(PyObject* object)
{
object->ob_type->tp_print(object); // 类型对象实际上定义了一系列该对象具备的行为
}

通过泛型指针, 实现多态的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
[object.h]
#define _Py_NewReference(op) ((op)->ob_refcnt = 1) // 初始化对象时,引用计数设为1
#define _Py_Dealloc(op) ((*(op)->ob_type->tp_dealloc)((PyObject *)(op))) // 调用析构函数, 但是需要注意的是, 其并不意味着最终一定会调用free释放内存空间, 垃圾回收应该是惰性的, 因此在析构时,通常都是将对象占用的空间归还到内存池中(内存再利用)
#define Py_INCREF(op) ((op)->ob_refcnt++) // 增加引用计数
#define Py_DECREF(op) \ // 减少引用计数
if (--(op)->ob_refcnt != 0) \
; \
else \
_Py_Dealloc((PyObject *)(op))

/* Macros to use in case the object pointer may be NULL: */
#define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)
#define Py_XDECREF(op) if ((op) == NULL) ; else Py_DECREF(op)

本文标题:Python 源码剖析 第一章

文章作者:定。

发布时间:2019年3月26日 - 00时03分

本文字数:4,357字

原始链接:http://cocofe.cn/2019/03/26/python-源码剖析读书笔记/

许可协议: Attribution-NonCommercial 4.0

转载请保留以上信息。