python 源码剖析0~2章

准备工作

前言,这本书比较早,源码剖析是针对python2.5

1.python的总体架构

python架构主要分为三部分

  • 从左到右分别为,python文件,python解释器,运行时环境
    • python解释器:
      • scanner 词法分析,将代码且分为一个个token
      • parser语法分析建立AST
      • compiler根据AST生成python 字节码
      • code Evaluator(虚拟机)执行这些字节码
    • 运行时环境:
      • 内建对象:list,dict等
      • 内存分配器:和malloc的一层接口
      • 运行状态信息: 维护解释器在执行字节码时的不同状态(还不知道具体是怎样的)

2.python源码的组织

  • include: python 提供的所有头文件,用于用户通过c或c++来编写自定义模块
  • Lib: 包含所有python自带标准库,用python编写的
  • Modules: c编写的模块
  • parser: 解释器Scanner和Parser部分
  • Objects: 内建对象,list, dict,整数等等
  • Python: Compiler和执行引擎部分

一些tips: api中以NEW结尾当做c++ new, Malloc结尾的当做c mallor

一.python对象初探

python中类,对象,都是通过对象来实现的

1.python内的对象

  • 人的视角: 对象是数据和基于这些数据操作的集合
  • 计算机视角: 对象是一片被分配的内存空间(连续或离散),可以在更高层次上作为一个整体来考虑,这个整体就是对象
  • python 所有内建类型对象是静态初始化的
  • python 一个对象一旦被创建,它内存中的大小就是不可变的(通过指针来定向变长数据)

1.1 PyObject

// object.h
#define PyObject_HEAD     \
    _PyObject_HEAD_EXTRA    \  // 一般情况下为空
    int ob_refcnt;  // 引用计数
    struct _typeobject *ob_type; //指定一个对象类型的类型对象
typedef struct _object{
    PyObject_HEAD
} PyObject;
复制代码
  • 每一个python对象除了PyObject之外还需要额外的内存,PyObject定义了必须要的数据比如PyIntObject
// intobject.h
typedef struct _object{
    PyObject_HEAD
    long ob_ival;
} PyIntObject;
复制代码
  • 对于变长对象python有新的抽象
// object.h
#define PyObject_VAR_HEAD     \
    PyObject_HEAD    \
    long ob_size;       /一般指容器内元素数量
typedef struct _object{
    PyObject_VAR_HEAD
} PyVarObject;
复制代码

1.2类型对象

PyObject占用内存大小是对象的元信息,元信息和对象所属类型密切相关(下一节介绍)

// object.h
typedef struct _typeobject{
    PyObject_VAR_HEAD
    char *tp_name;  //print 信息"<module>.<name>"
    int tp_basicsize, item_size; //为了分配内存大小
    destructor tp_dealloc;
    printfunc tp_print;
    hashfunc tp_hash;
    ternaryfunc tp_call;
    ...
} PyTypeObject;
复制代码

1 对象的创建

python对象的创建主要有两种方式

  • C API(对于python的内建对象, 直接分配内存)
    • 泛型API AOL
    • 类型相关API COL
  • 类型对象创建(用户自定义对象,因为不可能事先提供这类C方法),大致流程如下
    • 会调用ob_type 所指定的PyTyoeObject类的 tp_new方法,如果tp_new是NULL会追溯tp_base所指向的ob_type的tp_new方法,最终定位的tp_new(因为所有类继承object会有保底的tp_new方法)负责内存的申请(类似c++的new)
    • 之后通过tp_init初始化(类似c++的构造函数)

2 类型的类型

PyTypeObect 实际也是有ob_type属性的,其为PyType_Type(即type, 负责PyTypeObect的创建,即metaclass)

下图以int 为例说明了这些关系,也就是所知的python里面class,object,type的关系

其他略

二.python中的整数对象

主要是PyIntObject和PyInt_Type,其他和普遍的PyObject, PyTyoeObject没什么,值得关注的有

  • python2中稍小一点的数直接用C语言中的long去存储,稍大一点的数(超过long的承受范围)会使用python的long对象去存储,而python3不会作区分,统一用longObect去存储,实现用到了柔性数组,感兴趣可以查一下
  • 小整形数组的内存池和大整形对象的内存链的维护,避免频繁malloc,后面主要介绍下

2.1 小整形对象

使用了以前分配好的对象池

// [intobject.c]
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that they
   can be shared.
   The integers that are saved are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif
复制代码

2.2 大整形对象

对于其他整数,Python运行环境将会提供一块内存空间,这块内存空间由这些对象轮流使用,避免了频繁malloc。 在Python通过PyIntBlock结构,实现这个机制。

// [intobject.c]

#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE      8       /* Enough for a 64-bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
复制代码

可以创建int的代码理解这两块的使用

// [intobject.c]
PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* 尝试使用小整数对象池 */
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
        return (PyObject *) v;
    }
#endif
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)v->ob_type;  /* 有一个看似不合适但是比较方便的地方,freelist会通过 ob_type存放可用空间的pyObject的地址(类似链表的next),而不是 PyTyoeObject */
    (void)PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}
复制代码

下面是关于freelist的申请,和freelist和block_list的维护有关的代码

// [intobject.c]
static PyIntObject *
fill_free_list(void)
{
    PyIntObject *p, *q;
    /* 申请大小为sizeof(PyIntBlock)的内存空间,并链接到已有的block list中 */
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    ((PyIntBlock *)p)->next = block_list;
    block_list = (PyIntBlock *)p;
    /* 将PyIntBlock中的PyIntObject数组——objects转变成单向链表*/
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        q->ob_type = (struct _typeobject *)(q-1); /* 上一段代码中所提到的不合适的地方
    Py_TYPE(q) = NULL;
    return p + N_INTOBJECTS - 1;
}
复制代码

这样,freelist会指向可以分配内存的地址,但是如果由之前分配的PyIntObject被释放了,freelist需要将被释放的地址重新使用才可以,这个是通过PyIntObect的析构函数来实现的

// [intobject.c]
static void
int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) {   // 如果不是派生类这么执行,保证freelist的完整性
        v->ob_type = (struct _typeobject *)free_list;
        free_list = v;
    }
    else                        // 如果是派生类,则执行正常的析构流程
        v->ob_type->tp_free((PyObject *)v);
}
复制代码

转载于:https://juejin.im/post/5cffb9b76fb9a07eb94f8303

猜你喜欢

转载自blog.csdn.net/weixin_33722405/article/details/93175189