Python内核阅读(一): 对象的基石--PyObject

Python 2017-08-01

起步

为了了解python的整体架构,也为了提升自我能力,学习他人优秀代码.

源码获取

源码从github上获取, 不仅可以自由切换版本,也可以跟进作者的更新.

git clone https://github.com/python/cpython

我看的是3.6.2版本的,这是我阅读时候的最新版本, 切换版本用:

git checkout v3.6.2

目录介绍

  • Include: 包括Python提供的所有头文件, 可以用于c/c++扩展
  • Lib: Python的标准库, 全部都是用python写的
  • Modules: 包含了C语言编写的模块, 比如random, StringIO
  • Parser: 包含了python解释器中的scanner和parser部分,也就是词法分析和语法分析部分,一个类似yacc一样根据规则自动生成
  • Objects: 包含所有Python的内置对象,整数, list, dict等.也包含了运行时python需要的所有内部使用的对象的实现
  • Python: 包含了python解释器中Compiler和执行引擎部分,是python运行的核心所在
  • PCBuild:包含了vs工程文件

修改源代码

修改源代码只要有个编辑器, 修改后保存就OK了, 一般情况下, 输出是最常用的, 跟踪运行情况和过程使用 printf 最简单, 但是输出python中的对象就不那么方便了, 有时候就需要调用下python的C API了:

PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);

总体架构

Python的整体架构可分为3个模块,一个是内建模块. 一个是python内核, 一个是python虚拟机.

内建模块就诸如 import os 时,这个os就是Python的内建模块.Python内核就包括定义Python的对象/类型系统,处理垃圾回收机制等.虚拟机就是解释器,对Python代码进行词法语法分析等.

PyObject

了解python的人都知道, python中所有的东西都被当做是对象来处理, 源码中是这样定义对象的结构的:

[object.h]
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

先看下来第一个 _PyObject_HEAD_EXTRA 的定义:

[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

意思是说如果定义了 Py_TRACE_REFS 则对象结构会变成是一个双向列表, 所有堆中活动的对象都在这列表中. 在release下没有定义 Py_TRACE_REFS, 因此这个宏可以忽略.

追踪 Py_ssize_t 可以发现:

[pyport.h]
typedef Py_intptr_t     Py_ssize_t;

[pyport.h]
typedef intptr_t        Py_intptr_t;

继续追踪:

[vcstdint.h]
#ifdef _WIN64 // [
   typedef __int64           intptr_t;
   typedef unsigned __int64  uintptr_t;
#else // _WIN64 ][
   typedef _W64 int               intptr_t;
   typedef _W64 unsigned int      uintptr_t;
#endif // _WIN64 ]

也就是说在64位系统中, 它是64位int整型, 32位系统就是int. _W64 是为了兼容64位系统存在的. 所以 Py_ssize_t 的本质就是 int. PyObject 可以简化成:

# 简化
typedef struct _object {
    int ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

成员作用

分析一下 PyObject 中的两个成员的作用.

ob_refcnt 表示变量引用次数, python的垃圾回收机制基于引用计数的, 当某个对象引用计数减少到0时, 就可以将该变量从堆上删除,释放内存, ob_refcnt 与内存管理机制有关.

ob_type 表示了对象的类型信息, 诸如int, string, function等.

因此 PyObject 这个结构只有两个内容:一个引用计数, 一个类型信息. 似乎少了一些"内容", 比方说python代码中 a = 1 , 结构体的引用计数为1, 类型为整型, 却没有保存 1 和变量名 a .

这样说明, PyObject 仅仅是对象的共有的部分, 在使用上还需要"其他的内容".

[longobject.h]
typedef struct _longobject PyLongObject;

[longintrepr.h]
typedef uint32_t digit;
struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

在python3中,所有整型都是视为长整型处理,它的值可以任意大小,不再担心溢出问题. python2中有int和long的区分, 所以在2中有 intobject.h.

定长对象与变长对象

在c语言中, 整型占用内存大小是固定的, 无论你保存1还是100. 而由于c没有字符串类型, 因此要表示字符串得用N个char组成数组. 在python中这样描述变长对象:

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

ob_size 表示容纳元素的个数, 不是对象占用的内存字节数.

int string list dict
ob_refnt ob_refnt ob_refnt ob_refnt
ob_type ob_type ob_type ob_type
ob_digit[1] ob_size ob_size 其他信息
其他信息 其他信息

类型对象

不同对象所需要申请的内存大小也是不一样的, 在 PyObject 结构中就有一个成员来描述变量的类型:

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

_typeobject 中包含了很多信息,主要有:

  1. 类型名,tp_name,格式为.,用于内部调用和方便调试
  2. 创建对象时分配内存空间大小的变量, tp_basicsizetp_itemsize,分别代表基本的大小和里面元素的大小
  3. 与对象相关的操作,如tp_print这样的函数
  4. 要描述的本类型的其他信息

本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧