Python对象模型

阅读条件:

C语言基础, Python爱好者


<!--more-->

本系列是阅读Python源码剖析的读书笔记.

所有Python的语言魔法都有一个朴素的解释.

近一年以来,一直在使用Python完成项目,时常惊叹于Python语法的简洁优雅.PEP 20 The Zen of Python描述了Python的指导原则.

我也读过类似Effective Python:59个方法Solid Python:91个建议这样的书籍,并且尝试在实际项目使用学习到的Tips.但是总有些隔靴搔痒的意思.

对于一些语言特性,比如元类,装饰器,生成器,弱类型等,总是有一种雾里看花的感觉.惊叹于这些语言特性的魔力,但是要看穿魔法的迷雾还是需要深入到Python的源码实现中去.

我们都知道原始Python语言本身是用C语言实现的.本系列就是阅读Python源码剖析后的读书笔记.

我的读书笔记主要基于自己已有的知识,蜻蜓点水般的记录了自己的总结和收获.更多精彩的内容还是要到书中去捡拾.

前言

Python实现主要分为3个主要部分:

  1. 模块和库
  2. Python虚拟机
  3. 运行时环境: Python对象模型, 内存分配, 运行状态

我们同样遵循书中的提纲,本篇主要介绍Python对象模型.

即使接触Python不久的同学可能也知道,Python中一切都是对象.整数是整数对象,字符串是字符串对象,函数是函数对象甚至类的定义本身也是一个对象.

那么在源码层面,这种一切都是对象的机制是怎么做到的呢?这也是本文的核心问题:

如何用C语言实现不同对象的对象模型?甚至对外暴露统一的接口

源码目录与结构

书中源码是Python 2.5.0,涉及到的主要代码在IncludeObjects目录中.前者是头文件声明,后者根据不同的对象在不同的文件中实现了对应的方法.

Python对象

初识

在Python源码中,我们经常看到的一个结构体就是PyObject,似乎每个Python中的对象都可以用这个结构体来描述.下面我们就来看下这个结构体的定义:

1
2
3
4
5
6
7
8
9
[object.h]
typedef struct _object {
PyObject_HEAD
} PyObject;
#define PyObject_HEAD \
int ob_refcnt; \
struct _typeobject *ob_type;

源码还是解释的比较清楚的,PyObject仅仅包含了一个叫做PyObject_HEAD的东西.顾名思义,这大概是表示一个Python对象的头部.

我们将上面的代码合并,看看头部中到底有哪些东西.

1
2
3
4
typedef struct _object {
int ob_refcnt;
struct _typeobject *ob_type;
} PyObject;

可以看到,首先是引用计数,不用想这一定是和内存回收有关系的,我们先跳过.其次,是一个指向类型结构体的指针.

对象类型

接着我们来看看struct _typeobject是怎么一回事.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[object.h]
typedef 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;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
cmpfunc tp_compare;
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
...
...
} PyTypeObject;

这段代码还是比较长的,我们大致看看.也能猜出其中不少的含义. 其中通过tp_name描述了类型的名称,通过tp_basicsizetp_itemsize描述了对象的大小. 后面还有一系列的对象方法.

对象数据

我们在前面已经看到对象的类型,方法都已经有了.那么对象本身的数据呢?我们通过最简单的一个对象类型来看一看.

1
2
3
4
5
[intobject.h]
typedef stuct {
PyObject_HEAD
long ob_ival;
} PyIntObject;

深入一层,我们来看PyInt_Type的定义:

1
2
3
4
5
6
7
8
9
10
11
12
[intobject.c]
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"int",
sizeof(PyIntObject),
0,
(destructor)int_dealloc, /* tp_dealloc */
(printfunc)int_print, /* tp_print */
...
...
};

可以看到对于Int类型的对象而言,数据就跟在PyObject_HEAD的后面.而在类型信息中,则规定它的各种对应操作.

其他

缓冲池

在常见的内建对象类型中,包括整数,字符串,列表,字典中,都使用了类似的缓存机制. 本质上,就是希望尽可能少的使用到系统调用,同时尽可能高效使用内存.

模拟Small Python

作者提供了一段非常简洁的C++代码,模拟了简单的Python行为.我没有找到原始代码,但是在github上我找到一份.

我将部分warning修复后,也放到了GitHub上,这是本文最有价值的部分.

总结

本文描述了Python的对象模型的基础.Python对象模型是通过C来实现的,那么如何使用C语言实现面向对象的语言.在我们的示例代码中给出了范例.