阅读源码版本python 3.8.3

参考书籍<<Python源码剖析>>

参考书籍<<Python学习手册 第4版>>

官网文档目录先容

  1. Doc目录主要是官方文档的说明。

  2. Include:目录主要包罗了Python的运行的头文件。

  3. Lib:目录主要包罗了用Python实现的尺度库。

  4. Modules: 该目录中包罗了所有用C语言编写的模块,好比random、cStringIO等。Modules中的模块是那些对速率要求异常严酷的模块,而有一些对速率没有太严酷要求的模块,好比os,就是用Python编写,而且放在Lib目录下的

  5. Objects:该目录中包罗了所有Python的内建工具,包罗整数、list、dict等。同时,该目录还包罗了Python在运行时需要的所有的内部使用工具的实现。

  6. Parser:该目录中包罗了Python注释器中的Scanner和Parser部门,即对Python源码举行词法剖析和语法剖析的部门。除了这些,Parser目录下还包罗了一些有用的工具,这些工具能够凭据Python语言的语法自动天生Python语言的词法和语法剖析器,将python文件编译天生语法树等相关事情。

  7. Programs目录主要包罗了python的入口函数。

  8. Python:目录主要包罗了Python动态运行时执行的代码,内里包罗编译、字节码注释器等事情。

1. Run Python文件的启动流程

Python启动是由Programs下的python.c文件中的main函数最先执行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

1.1 初始化要害流程

  • 初始化一些与设置项 如:开启utf-8模式,设置Python内存分配器
  • 初始化pyinit_core焦点部门
    • 建立生命周期 pycore_init_runtime, 同时天生HashRandom
    • 初始化线程和注释器并建立GIL锁 pycore_create_interpreter
    • 初始化所有基础类型,list, int, tuple等 pycore_init_types
    • 初始化sys模块 _PySys_Create
    • 初始化内建函数或者工具,如map, None, True等 pycore_init_builtins
      • 其中包罗内建的错误类型初始化 _PyBuiltins_AddExceptions

Python3.8 对Python注释器的初始化做了重构PEP 587-Python初始化设置

1.2 run 相关源码阅读

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //执行python剧本

	if (Py_FinalizeEx() < 0) {  // 释放资源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 释放资源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 获取一个持有GIL锁的注释器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模块名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 执行剧本
}

// 执行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leAVe __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 剖析传入的剧本,剖析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 将AST编译成字节码然后启动字节码注释器执行编译效果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 将AST编译成字节码
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 注释执行编译的字节码
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

1.3 字节码查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

执行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左边3, 7, 8示意 test.py中的第一行和第二行,右边示意python byte code

Include/oPCode.h 发现总共有 163 个 opcode, 所有的 python 源文件(Lib库中的文件)都会被编译器翻译成由 opcode 组成的 pyx 文件,并缓存在执行目录,下次启动程序若是源代码没有修悔改,则直接加载这个pyx文件,这个文件的存在可以加速 python 的加载速率。通俗.py文件如我们的test.py 是直接举行编译注释执行的,不会天生.pyc文件,想天生test.pyc 需要使用python内置的py_compile模块来编译该文件,或者执行命令python3 -m test.py python天生.pyc文件

1.4 python中的code工具

字节码在python虚拟机中对应的是PyCodeObject工具, .pyc文件是字节码在磁盘上的表现形式。python编译的历程中,一个代码块就对应一个code工具,那么若何确定若干代码算是一个Code Block呢? 编译历程中遇到一个新的命名空间或者作用域时就天生一个code工具,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject, 参考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置参数的个数,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的参数个数 PyIntObject
co_posonlyargcount Code Block 的位置参数个数 PyIntObject
co_kwonlyargcount Code Block 的要害字参数个数 PyIntObject
co_nlocals Code Block 中局部变量的个数 PyIntObject
co_stacksize Code Block 的栈巨细 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 对应的 .py 文件中的起始行号 PyIntObject
co_code Code Block 编译所得的字节码 PyBytesObject
co_consts Code Block 中的常量聚集 PyTupleObject
co_names Code Block 中的符号聚集 PyTupleObject
co_varnames Code Block 中的局部变量名聚集 PyTupleObject
co_freevars Code Block 中的自由变量名聚集 PyTupleObject
co_cellvars Code Block 中嵌套函数所引用的局部变量名聚集 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 对应的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函数名/类名/模块名 PyUnicodeObject
co_lnotab Code Block 的字节码指令于 .py 文件中 source code 行号对应关系 PyBytesObject
co_opcache_map python3.8新增字段,存储字节码索引与CodeBlock工具的映射关系 PyDictObject

1.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

分外收获: c 语言中 ##和# 号 在marco 里的作用可以参考 这篇

在宏界说里, ## 被称为连接符(concatenator) , a##b 示意将ab连接起来

a 示意把a转换成字符串,即加双引号,

以是LONAD_CONST这个指领凭据宏界说睁开如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 获取一个PyObject* 指针工具
    Py_INCREF(value);  // 引用计数加1
    PUSH(value);     // 把刚刚建立的PyObject* push到当前的frame的stack上, 以便下一个指令从这个 stack 上面获取
    goto fast_next_opcode;

1.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UNBoundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虚拟机中,注释器主要在一个很大的循环中,不停地读入 opcode, 并凭据 opcode 执行对应的指令,当执行完所有指令虚拟机退出,程序也就竣事了

1.6 总结

历程形貌:

  1. python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机遇从编译获得的PyCodeObject工具中一条一条执行字节码指令,并在当前的上下文环境中执行这条字节码指令,从而完成程序的执行。Python虚拟机实际上是在模拟操作中执行文件的历程。PyCodeObject工具中包罗了字节码指令以及程序的所有静态信息,但没有包罗程序运行时的动态信息——执行环境(PyFrameObject),后面会继续纪录执行环境的阅读。
  2. 从整体上看:OS中执行程序离不开两个观点:历程和线程。python中模拟了这两个观点,模拟历程和线程的划分是PyInterpreterStatePyTreadState。即:每个PyThreadState都对应着一个帧栈,python虚拟机在多个线程上切换(靠GIL实现线程之间的同步)。当python虚拟机最先执行时,它会先举行一些初始化操作,最后进入PyEval_EvalFramEx函数,内部实现了一个main_loop它的作用是不停读取编译好的字节码,并一条一条执行,类似CPU执行指令的历程。函数内部主要是一个switch结构,凭据字节码的差别执行差别的代码

2. Python中的Frame

如上所说,PyCodeObject工具只是包罗了字节码指令集以及程序的相关静态信息,虚拟机的执行还需要一个执行环境,即PyFrameObject,也就是对系统栈帧的模拟。

2.1 堆和栈的熟悉

堆中存的是工具。栈中存的是基本数据类型和堆中工具的引用。一个工具的巨细是不能估量的,或者说是可以动态转变的,但是在栈中,一个工具只对应了一个4btye的引用(客栈星散的利益)

内存中的客栈和数据结构客栈不是一个观点,可以说内存中的客栈是真实存在的物理区,数据结构中的客栈是抽象的数据存储结构。

内存空间在逻辑上分为三部门:代码区,静态数据区和动态数据区,动态数据区有分为堆区和栈区

  • 代码区:存储的二进制代码块,高级调剂(作业调剂)、中级调剂(内存调剂)、低级调剂(历程调剂)控制代码区执行代码的切换
  • 静态数据区:存储全局变量,静态变量,常量,系统自动分配和接纳。
  • 动态数据区:
    • 栈区(stack):存储运行方式的形参,局部变量,返回值,有编译器自动分配和接纳,操作类似数据结构中的栈
    • 堆区(heap):new一个工具的引用或者地址存储在栈区,该地址指向指向工具存储在堆区中的真实数据。如c中的malloc函数,python中的Pymalloc

2.2 PyFrameObject工具

typedef struct _frame{  
    PyObject_VAR_HEAD //"运行时栈"的巨细是不确定的, 以是用可变长的工具
    struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表  
    PyCodeObject *f_code; //PyCodeObject 工具,这个frame就是这个PyCodeObject工具的上下文环境  
    PyObject *f_builtins; //builtin名字空间  
    PyObject *f_globals;  //global名字空间  
    PyObject *f_locals;   //local名字空间  
    PyObject **f_valuestack; //"运行时栈"的栈底位置  
    PyObject **f_stacktop;   //"运行时栈"的栈顶位置  
    //...  
    int f_lasti;  //上一条字节码指令在f_code中的偏移位置  
    int f_lineno; //当前字节码对应的源代码行  
    //...  
      
    //动态内存,维护(局部变量+cell工具聚集+free工具聚集+运行时栈)所需要的空间  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

若是你想知道 PyFrameObject 中每个字段的意义, 请参考 Junnplus' blog 或者直接阅读源代码,领会frame的执行历程可以参考zpoint'blog.

名字空间实际上是维护着变量名和变量值之间关系的PyDictObject工具。
f_builtins, f_globals, f_locals名字空间划分维护了builtin, global, local的name与对应值之间的映射关系。

每一个 PyFrameObject工具都维护了一个 PyCodeObject工具,这表明每一个 PyFrameObject中的动态内存空间工具都和源代码中的一段Code相对应。

2.2.1 栈帧的获取,事情中会用到

可以通过sys._getframe([depth]), 获取指定深度的PyFrameObject工具

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

2.2.2 python中变量名的剖析规则 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 示意局部变量

  • Enclosed 示意嵌套的变量

  • Global 示意全局变量

  • Built-In 示意内建变量

若是这几个顺序都取不到,就会抛出 ValueError

可以在这个网站python执行可视化网站,考察代码执行流程,以及变量的转换赋值情形。

3. 分外收获

意外收获: 之前知道pythonGIL , 遇到I/O壅闭时会释放gil,现在从源码中看到了对应的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

参考:

python 源码剖析 基本篇

python虚拟机运行原理

,

Allbet Gaming

www.xiangxiren12.com欢迎进入欧博平台网站(Allbet Gaming),Allbet Gaming开放欧博平台网址、欧博注册、欧博APP下载、欧博客户端下载、欧博真人游戏(百家乐)等业务。

发布评论

分享到:

潍坊新闻综合频道专题:【金汇动向】 油价下试低位 加元有下行压力
2 条回复
  1. 欧博allbet注册
    欧博allbet注册
    (2020-08-15 00:03:56) 1#

    AllbetGmaing客户端下载欢迎进入AllbetGmaing客户端下载(www.aLLbetgame.us):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。嘿嘿,我来过呀

  2. 欧博allbet注册
    欧博allbet注册
    (2020-08-15 00:03:58) 2#

    AllbetGmaing客户端下载欢迎进入AllbetGmaing客户端下载(www.aLLbetgame.us):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。嘿嘿,我来过呀

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。