《python解释器源码剖析》第0章--python的架构与编译python

摘要:
本系列基于陈如先生的“Python源代码分析”作为学习材料和记录的学习内容。在Python中创建对象时,内存分配器完全负责内存应用程序。事实上,它是Python运行时和C中的malloc之间的一个层接口。在解析器中,箭头的方向指示Python操作期间数据流的方向。这是与Python中的列表实现相关的源文件。

本系列是以陈儒先生的《python源码剖析》为学习素材,所记录的学习内容。不同的是陈儒先生的《python源码剖析》所剖析的是python2.5,本系列对应的是python3.7,所以某些地方会和原著有出入,另外我在介绍的过程中会穿插大量的python代码,不仅仅是介绍如何实现的,还会使用python实际地对我们的结论进行演示。下面就开始吧。不过在开始分析python的实现之前,我们有很多的准备工作要做。比如,首先应该了解一下python的整体架构,来对python的实现有一个宏观的认识

0.1 python的总体架构

废话不多说,先来看一张python的总体架构图。

《python解释器源码剖析》第0章--python的架构与编译python第1张

如图所示。在最高的层次上,python的总体架构可以分为三个主要的部分。图的左边,是python所提供的的大量的模块、库、以及用户自定义的模块。比如在执行import os的时候,这个os就是python内建的模块,当然用户还可以通过自定义模块来扩展python系统。

在图的的右边,是python的运行时环境,包括对象/类型系统(Object/Type Structures)、内存分配器(Memory Allocator)、和运行时状态信息(Current State of Python)。运行时状态维护了解释器在执行字节码时不同的状态(比如正常状态和异常状态)之间切换的动作,我们可以将它视为一个巨大而复杂的有穷状态机。内存分配器则全权负责python中创建对象时,对内存的申请工作,实际上它就是python运行时与C中malloc的一层接口。而对象/类型系统则包含了python中存在的各种内建对象,比如:整数、list、dict,以及各种用户自定义的类型和对象。

在中间的部分,可以看到python的核心--解释器(interpreter),或者称之为虚拟机。在解析器中,箭头的方向指示了python运行过程中的数据流方向。其中scanner对应词法分析,将文件输入的python源代码或者从命令行输入的一行行python代码切分为一个个的token;parser对应语法分析,在scanner的分析结果上进行语法分析,建立抽象语法树(Abstract Syntax Tree,简称AST );Compiler则是对AST进行编译,生成指令集合--也就是python字节码(byte code);最后由Code Evaluator来执行这些字节码。因此Code Evaluator又可以被称为虚拟机。

Code Evaluator和Object/Type Structure之间的箭头表示使用关系;而与Current State of Python之间的箭头表示修改关系,即python在执行的过程中会不断地修改当前解释器所处的状态,在不同的状态之间切换。

0.2 python源代码的组织

python的源代码可以在python的官网www.python.org中下载,下载完源码的压缩包并解压之后,可以看到如下的目录结构。

《python解释器源码剖析》第0章--python的架构与编译python第2张

  • Include该目录包含了python所提供的的所有头文件,如果用户需要自己使用C或者C++来编写自定义模块扩展python,那么就需要用到这里的头文件
  • Lib该目录包含了python自带的所有标准库,Lib中的库基本上都是使用python编写的
  • Modules该目录中包含了所有用C语言编写的模块,比如random、io等。Modules中的模块是那些对速度要求非常严格的模块,而有一些对速度没有太严格要求的模块,比如os,就是用python编写,并且是放在Lib目录下的。
  • Parser该目录中包含了python解释器中的Scanner和Parser部分,即对python源代码进行词法分析和语法分析的部分。除了这些,Parser还包含了一些有用的工具,这些工具能够根据python语言的语法自动生成python语言的词法和语法分析器,与YACC非常类似
  • Objects该目录包含了所有python的内建对象,包括整数、list、tuple、dict、set等等。同时,该目录还包含了python在运行时需要的所有内部使用对象的实现。
  • Python该目录包含了python解释器中Compiler(编译)和Code Evaluator(执行)两部分,是python运行的核心所在

0.3 修改python源代码

怎么上来就修改python源代码,别慌,只是简单的做一个小小的trick。

//文件:Objects/listobject.c。这是python中与list实现有关的源文件。

//可以看到这个函数主要用来改变列表的容量的。
static int
//关于这里的Py_ssize_t,这是python自定义的类型,就把它当成int即可
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    PyObject **items;
    size_t new_allocated, num_allocated_bytes;
    Py_ssize_t allocated = self->allocated;
	//这里的allocated是当前list对象所拥有的容量,注意不是list对象的长度(或者说元素个数),而是容量
    //比如 l = [1, 2, 3, 4, 5, 6, 7, 8],如果是使用[]这种中括号的方式创建列表的话。那么显然这里的l的长度和容量都为8。
    //而这里的newsize是,当我们append或者extend新元素之后,对应的list对象的长度
    //当使用l.append(9)的时候,说明了什么,说明了newsize变成了9
    //可是原来的allocated(容量)是8啊,所以要进行扩容了。
    //看这里的if条件,显然allocated >= newsize已经不满足了,因为要存储的元素的个数超过了容量,要扩容了
    if (allocated >= newsize && newsize >= (allocated >> 1)) {
        assert(self->ob_item != NULL || newsize == 0);
        Py_SIZE(self) = newsize;
        return 0;
    }
	
    //扩容之后的容量为new_allocated = newsize + newsize >> 3 + newsize < 9 ? 3 : 6
    //所以将9带入进入,得到 9 + 1 + 6 = 16,这便是新分配的容量
    
    /*
        l = [1, 2, 3, 4, 5, 6, 7, 8]
        print((l.__sizeof__() - 40) // 8)  # 8

        l.append(9)
        print((l.__sizeof__() - 40) // 8)  # 16    
    */
    //使用python进行测试也验证了这一点
    new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
    if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) {
        PyErr_NoMemory();
        return -1;
    }

    if (newsize == 0)
        new_allocated = 0;
    num_allocated_bytes = new_allocated * sizeof(PyObject *);
    //将新添加的元素的指针加进列表里面
    items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
    if (items == NULL) {
        PyErr_NoMemory();
        return -1;
    }
    //让ob_item指向为新的items
    self->ob_item = items;
    Py_SIZE(self) = newsize;
    //将这里的allocated变为新分配的容量
    self->allocated = new_allocated;
    return 0;
}

// 中间省略了一部分


//可以看到这是通过索引获取list对象内部元素的实现
static PyObject *
list_item(PyListObject *a, Py_ssize_t i)
{
    //这里的i就是我们传入的索引。
    //如果i小于0或者i大于op(对应的list对象)的最大索引
    if (i < 0 || i >= Py_SIZE(a)) {
        //当然python中list对象也支持负数索引,因此还会有其他的检测
        if (indexerr == NULL) {
            //PyUnicode_FromString可以理解为在python代码报错的时候,存储打印信息的函数
            indexerr = PyUnicode_FromString(
                //看这行代码熟悉不,小伙伴们,索引越界是不是报这个错误啊
                "list index out of range");//我们将原本的信息改一下,改成"兄嘚,您列表索引越界了"
            if (indexerr == NULL)
                return NULL;
        }
        //设置异常,这是一个IndexError,indexerr则是异常信息
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    Py_INCREF(a->ob_item[i]);
    //如果i为是正常索引,直接返回。
    return a->ob_item[i];
}

0.4 编译python

编译python:只介绍如何在linux下编译python。编译的过程分为三步

  • 进入python的主目录下,执行:./configure -prefix=你期望python安装到的路径
  • make
  • make install

其中2、3两步可以合为一步,即make && make install,下面就用我们刚才修改完的python源码进行编译

《python解释器源码剖析》第0章--python的架构与编译python第3张

可以看到异常,已经被我们从底层重新定义了。

0.5 注意事项

  • 在早期版本,python的许多数值的类型都是int或者long,现在Python自己定义了一个新类型,Py_ssize_t,凡出现这个类型的地方,就把它当成long看待即可
  • python的内存管理机制是比较复杂的,我们会在后面剖析。但是很快你就会看到对内存管理接口的使用。这是因为创建对象必先分配内存,而分配内存必须通过内存管理接口,所以我们在这里提一下。通常Python的源码中会使用PyObject_GC_NEW、PyObject_GC_Malloc、PyMem_MALLOC,PyObject_MALLOC等API。只要坚持一个原则即可,凡是以New结尾的,都当成是C++中new操作符即可,凡是以Malloc结尾的,都当成是C中的malloc操作符即可。

《python解释器源码剖析》第0章--python的架构与编译python第4张

免责声明:文章转载自《《python解释器源码剖析》第0章--python的架构与编译python》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇win7 64位系统使用VS2010生成时出现中文目录乱码问题的解决方法利用dd命令制作u盘iso镜像下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python 环境-----项目的包,打包方法

一. 将本地开发环境的依赖项目生成清单文件 1.在本地的开发环境中,env下执行: >pip3 freeze >requirements.txt 清单文件将会生成在当前项目目录下,内容如下所示将生成后的文件上传到linux服务器, requirements.txt 清单中的包 certifi==2018.4.16chardet==3.0.4i...

Python+Apache环境搭建

Python+Apache环境搭建 Python+apache搭建时需要apache拓展mod_wsgi模块,apache启动时自动加载mod_wsgi模块,通过mod_wsgi与python通信,实现python部署在apache上。 安装 1.下载源码包mod_wsgi 2.解压缩mod_wsgi包 tar zxvf mod_wsgi-4.6.8.t...

groovy初体验:groovy在java中的应用

第一次接触groovy,主要是在java中嵌入groovy脚本,因为groovy和java的融合度非常好。 先放上来第一次写的一段groovy: 其实它完成的就是一句java的system.out操作,主要为了理解binding在groovy中的应用 说一下背景,这里的功能是为了打印日志,本来用的是system.out,结果师父为了让我接触一下groov...

python(一):python语言基础

一、python语言基本的8个要素 Python语言的8个要素:数据类型、对象引用、组合数据类型、逻辑操作符、运算操作符、控制流语句、输入/输出、函数的创建与引用。除此之外还有一个非常重要且无处不在的要素:对象。实际上,在所有的语言当中,这种要素结构几乎一样。   1.要素1--数据和对象类型 python的数据类型是指内置数据类型。python基本的内置...

Python字符串格式化

http://www.cnblogs.com/JerySpace/archive/2010/12/17/1909621.html 字符串的格式化 在python中也有类似于c中的printf()的格式输出标记。在python中格式化输出字符串使用的是%运算符,通用的形式为 格式标记字符串 % 要输出的值组 其中,左边部分的”格式标记字符串“可以完全和c中...

python 多线程批量传文件

#!/usr/bin/env python #_*_ coding:utf-8 -*-#autho:leiyong#time:2017-06-05#version: 1.3 import paramiko from conf import * from threading import Thread import sys user = 'root'...