Python3源码分析

本文环境python3.5.2。
参考书籍<<Python源码剖析>>
python官网

Python3启动流程概述

本文基于python3分析其基本的运行过程。作为一门动态语言,python脚本在运行的过程中,实现了编译文件并执行编译文件的过程,这一过程都是基于c语言实现,首先开始介绍一下python3的基本信息。

Python3源码结构

在官网下载python3.5.2后,根据官网的文档目录介绍,主要目录的信息如下:

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

Include目录主要包括了Python的运行的头文件。

Lib目录主要包括了用Python实现的标准库。

Objects目录包括了内建对象。

Parser目录包括了python编译相关代码,将python文件编译生成语法树等相关工作。

Programs目录主要包括了python的入口函数。

Python目录主要包括了Python动态运行时执行的代码,里面包括编译、字节码解释器等工作。

Python的启动流程

根据官网文档目录介绍,启动的时候是运行Python的脚本,Python启动是由Programs下的python.c文件中的main函数开始执行,根据平台的不同选择执行不同类型的main函数,在此已Linux为例;

int
main(int argc, char **argv)
{...res = Py_Main(argc, argv_copy);...return res;
}

该函数主要就是执行了Py_Main函数,并将该函数的执行结果返回。

继续查看Py_Main函数的执行,在位于Modules/main.c中找到Py_Main函数,

int
Py_Main(int argc, wchar_t **argv)
{...Py_Initialize();                              // 初始化...{...if (sts == -1)sts = run_file(fp, filename, &cf);    // 执行python脚本}...Py_Finalize();                                // 释放相关资源...return sts;                                   // 返回执行结果
}

由此可看出,在调用初始化函数Py_Initialize之后,就会执行run_file方法,当Python脚本执行完成后,就调用Py_Finalize释放相关资源。分别来查看这些函数的执行。

Py_Initialize初始化函数

该函数位于Python/pylifecycle.c中,

void
Py_InitializeEx(int install_sigs)
{_Py_InitializeEx_Private(install_sigs, 1);
}void
Py_Initialize(void)
{Py_InitializeEx(1);
}

直接调用了_Py_InitializeEx_Private方法,并且传入了两个都为1的参数,

void
_Py_InitializeEx_Private(int install_sigs, int install_importlib)
{..._PyRandom_Init();                                                // random模块初始化interp = PyInterpreterState_New();                               // 初始化解释器if (interp == NULL)Py_FatalError("Py_Initialize: can't make first interpreter");tstate = PyThreadState_New(interp);                              // 初始化线程if (tstate == NULL)Py_FatalError("Py_Initialize: can't make first thread");(void) PyThreadState_Swap(tstate);                               // 设置tstate为当前运行线程#ifdef WITH_THREAD/* We can't call _PyEval_FiniThreads() in Py_Finalize becausedestroying the GIL might fail when it is being referenced fromanother running thread (see issue #9901).Instead we destroy the previously created GIL here, which ensuresthat we can call Py_Initialize / Py_Finalize multiple times. */_PyEval_FiniThreads();                                           // 如果脚本中是多线程运行则创建gil锁/* Auto-thread-state API */_PyGILState_Init(interp, tstate);
#endif /* WITH_THREAD */_Py_ReadyTypes();                                                // 初始化所有类型,如long,list等...interp->modules = PyDict_New();                                 // 设置解释器的modules为新建的Dict类型if (interp->modules == NULL)                                    // 如果字典没有生成成功则报错Py_FatalError("Py_Initialize: can't make modules dictionary");/* Init Unicode implementation; relies on the codec registry */if (_PyUnicode_Init() < 0)Py_FatalError("Py_Initialize: can't initialize unicode");if (_PyStructSequence_Init() < 0)Py_FatalError("Py_Initialize: can't initialize structseq");bimod = _PyBuiltin_Init();                                      // 内建对象的初始化,如map,None,True等if (bimod == NULL)Py_FatalError("Py_Initialize: can't initialize builtins modules");_PyImport_FixupBuiltin(bimod, "builtins");                      // 设置builtins为初始化后的modinterp->builtins = PyModule_GetDict(bimod);                     // 设置解释器的builtins字段为初始化后的内建对象if (interp->builtins == NULL)Py_FatalError("Py_Initialize: can't initialize builtins dict");Py_INCREF(interp->builtins);                                    // 增加内建对象的引用/* initialize builtin exceptions */_PyExc_Init(bimod);                                             // 导入内建的错误类型,如BaseException,Exception等sysmod = _PySys_Init();                                         // 初始化sys模块if (sysmod == NULL)Py_FatalError("Py_Initialize: can't initialize sys");interp->sysdict = PyModule_GetDict(sysmod);                     // 设置解释器的sys模块if (interp->sysdict == NULL)Py_FatalError("Py_Initialize: can't initialize sys dict");Py_INCREF(interp->sysdict);_PyImport_FixupBuiltin(sysmod, "sys");                          // 将初始化的sysmod设置成模块sysPySys_SetPath(Py_GetPath());                                    // 设置导入路径PyDict_SetItemString(interp->sysdict, "modules",interp->modules);...initmain(interp); /* Module __main__ */                         // 创建主main...
}

该函数主要进行了相关类型的初始化,并初始化了解释器,初始化了线程对象,并设置了相关的内建mod等初始化工作。当初始化工作完成后,就调用了run_file来解释并执行脚本。

run_file编译并执行

该函数位于main.c文件中,

static int
run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{...run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);   // 解释执行...return run != 0;
}

真正调用了位于Python/pythonrun.c中的PyRun_AnyFileExFlags方法来解释执行,

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;}elsereturn PyRun_SimpleFileExFlags(fp, filename, closeit, flags);       // 执行脚本
}

此处讨论的是脚本模式,此时就会进入PyRun_SimpleFileExFlags函数执行,

int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,PyCompilerFlags *flags)
{...else {...v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,closeit, flags);                      // 在没有pyc的情况下执行}...
}

此时就调用了PyRun_FileExFlags函数进行执行,

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

查看run_mod函数,

static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,PyCompilerFlags *flags, PyArena *arena)
{...co = PyAST_CompileObject(mod, filename, flags, -1, arena);      // 将AST编译成字节码... v = PyEval_EvalCode((PyObject*)co, globals, locals);            // 解释执行编译的字节码...
}

再将AST转换为字节码后,调用了PyEval_EvalCode来执行字节码,查看位于Python/ceval.c中的该函数,

PyObject *
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
{return PyEval_EvalCodeEx(co,globals, locals,(PyObject **)NULL, 0,(PyObject **)NULL, 0,(PyObject **)NULL, 0,NULL, NULL);
}
...PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,PyObject **args, int argcount, PyObject **kws, int kwcount,PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
{return _PyEval_EvalCodeWithName(_co, globals, locals,args, argcount, kws, kwcount,defs, defcount, kwdefs, closure,NULL, NULL);
}

直接调用了_PyEval_EvalCodeWithName函数来执行,

static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,PyObject **args, int argcount, PyObject **kws, int kwcount,PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,PyObject *name, PyObject *qualname)
{...retval = PyEval_EvalFrameEx(f,0);...
}

最终调用了PyEval_EvalFrameEx来执行,该函数就是字节码解释器,实现方式就是一个for循序依次执行字节码,然后调用相关函数去执行,解释出来的脚本,然后返回执行结果。

至此,run_file的大致执行流程如上。

Python3虚拟机框架

通过分析Python的大致执行流程,有了初步了解Python是经历了多少步骤然后调用了哪些函数来进行执行。现在继续分析一下Python的虚拟机框架是如何来实现Python脚本的执行的。

首先我们编写一个简单的Python脚本;

def show():print("show")def foo():print("foo")show()
foo()

在终端命令行中,直接输入:

python -m dis test.py

终端上打印出该脚本的字节码,如下:

  1           0 LOAD_CONST               0 (<code object show at 0x10f2cc8a0, file "test.py", line 1>)3 LOAD_CONST               1 ('show')6 MAKE_FUNCTION            09 STORE_NAME               0 (show)4          12 LOAD_CONST               2 (<code object foo at 0x10f2ccd20, file "test.py", line 4>)15 LOAD_CONST               3 ('foo')18 MAKE_FUNCTION            021 STORE_NAME               1 (foo)7          24 LOAD_NAME                0 (show)27 CALL_FUNCTION            0 (0 positional, 0 keyword pair)30 POP_TOP8          31 LOAD_NAME                1 (foo)34 CALL_FUNCTION            0 (0 positional, 0 keyword pair)37 POP_TOP38 LOAD_CONST               4 (None)41 RETURN_VALUE

从中可以看到两个函数foo和show生成了两个code对象,code对象是什么作用呢?

Python的code对象

在Python的编译过程中,一个代码块就对应一个code对象,那Python如何定义一个代码块呢?当编译过程中遇到一个新的命令空间或者作用域时就生成一个code对象,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject;

typedef struct {PyObject_HEADint co_argcount;        /* #arguments, except *args */      // 位置参数的个数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 */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 forco_name (used in both) and co_firstlineno (used only incomparisons).  This is done to preserve the name and line numberfor tracebacks and debuggers; otherwise, constant de-duplicationwould collapse identical functions/lambdas defined on different lines.*/unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */PyObject *co_filename;  /* unicode (where it was loaded from) */        // 文件名PyObject *co_name;      /* unicode (name, for reference) */        int co_firstlineno;     /* first source line number */                  // 第一行源码行数PyObject *co_lnotab;    /* string (encoding addr<->lineno mapping) SeeObjects/lnotab_notes.txt for details. */void *co_zombieframe;     /* for optimization only (see frameobject.c) */ PyObject *co_weakreflist;   /* to support weakrefs to code objects */   // 弱引用
} PyCodeObject;

至此,可知脚本编译生成的字节码都存放在co_code中,其他属性字段都是存放了code在编译过程中保存的相关信息,这就是这个code对象在字节码解释器中运行时需要的局部变量,变量名等都是从code的相关字段中取值,有了PyCodeObject对象后,是如何在Python中被执行的呢?此时还需要将PyCodeObject对象转换成PyFrameObject对象。

Python的frame对象

在Python字节码执行的一级,会将PyCodeObject对象包装成PyFrameObject,一个PyCodeObject对应一个PyFrameObject对象,由此可知,test.py中存在三个PyFrameObject对象,除了show和foo外,还有一个是整个脚本的字节码所对应的PyFrameObject对象。

typedef struct _frame {PyObject_VAR_HEADstruct _frame *f_back;      /* previous frame, or NULL */            // 上一个栈帧对象PyCodeObject *f_code;       /* code segment */                       // 对应的code对象PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */ // 内建对象PyObject *f_globals;        /* global symbol table (PyDictObject) */  // 全局名称空间PyObject *f_locals;         /* local symbol table (any mapping) */    // 本地名称空间PyObject **f_valuestack;    /* points after the last local */         // 栈低/* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.Frame evaluation usually NULLs it, but a frame that yields sets itto the current stack top. */PyObject **f_stacktop;                                                // 栈顶...int f_lasti;                /* Last instruction if called */          // 上一条执行命令/* Call PyFrame_GetLineNumber() instead of reading this fielddirectly.  As of 2.3 f_lineno is only valid when tracing isactive (i.e. when f_trace is set).  At other times we usePyCode_Addr2Line to calculate the line from the currentbytecode index. */int f_lineno;               /* Current line number */                 // 当前行数int f_iblock;               /* index in f_blockstack */char f_executing;           /* whether the frame is still executing */PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */     // 动态内存
} PyFrameObject;

由此可知,多个code对象就对应多个PyFrameObject对象,每一个栈帧都是像函数调用那样嵌套调用执行,此时继续分析Frame对象是如何Python的运行环境中执行。

Python3运行时环境

Python在运行的初始化阶段,在_Py_InitializeEx_Private函数中初始化了interp和tstate;

interp = PyInterpreterState_New();
if (interp == NULL)Py_FatalError("Py_Initialize: can't make first interpreter");tstate = PyThreadState_New(interp);
if (tstate == NULL)Py_FatalError("Py_Initialize: can't make first thread");

由此可知,interp对应的就是一个解释器对象,tstate对应在Python中就是一个线程对象,这两者是如何推动Python的执行呢?

typedef struct _is {struct _is *next;                           // 下一个解释器对象struct _ts *tstate_head;                    // 线程对象PyObject *modules;                          // 模块的列表PyObject *modules_by_index;                 PyObject *sysdict;                          // 系统模块列表PyObject *builtins;                         // 内建对象列表PyObject *importlib;                        // 导入模块的函数PyObject *codec_search_path;                PyObject *codec_search_cache;PyObject *codec_error_registry;int codecs_initialized;int fscodec_initialized;#ifdef HAVE_DLOPENint dlopenflags;
#endif
#ifdef WITH_TSCint tscdump;
#endifPyObject *builtins_copy;                    // 内建对象副本
} PyInterpreterState;

由PyInterpreterState定义可以看出,解释器对象提供基本的内建对象和模块的数据,再来查看PyThreadState对象的定义;

typedef struct _ts {/* See Python/ceval.c for comments explaining most fields */struct _ts *prev;                                       // 上一个线程对象struct _ts *next;                                       // 下一个线程对象PyInterpreterState *interp;                             // 依赖的解释器对象struct _frame *frame;                                   // 当前执行的帧...long thread_id; /* Thread id where this tstate was created */  // 线程id...} PyThreadState;

主要定义了上下线程对象,当前锁对应的解释器对象,和当前正在执行的栈帧。此时的栈帧就是将PyCodeObject生成的栈帧,在Python的启动阶段的时候会调用_PyEval_EvalCodeWithName函数,该函数中就是调用PyFrame_New函数生成新的执行栈帧;

    static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,PyObject **args, int argcount, PyObject **kws, int kwcount,PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,PyObject *name, PyObject *qualname)
{   ...f = PyFrame_New(tstate, co, globals, locals);...retval = PyEval_EvalFrameEx(f,0);...
}

此时就进入字节码解释器解释执行,当执行到另一个PyCodeObject对象时,就调用PyFrame_New生成一个对应的frame调用字节码解释器继续执行。该生成过程位于fast_function函数中,后文会对其进行详细分析。至此Python运行的大概原理与执行的方式基本分析完毕。

总结

Python的运行过程图可归纳如下所示,

本文只是简单的分析了启动过程和Python的一个大致的执行过程,其中细节或有疏漏错误的地方请批评指正,具体详细的过程如有兴趣可自行调试分析。

python3.5源码分析-启动与虚拟机相关推荐

  1. golang源码分析-启动过程概述

    golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...

  2. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

  3. 嵌入式之uboot源码分析-启动第二阶段学习笔记(下篇)

    接上部分---->嵌入式之uboot源码分析-启动第二阶段学习笔记(上篇) 注:如下内容来自朱老师物联网大讲堂uboot课件 3.2.14 CFG_NO_FLASH (1)虽然NandFlash ...

  4. Python3.5源码分析-垃圾回收机制

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的垃圾回收概述 随着软硬件的发展,大多数语言都已 ...

  5. Python3.5源码分析-sys模块及site模块导入

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的sys模块初始化 根据分析完成builtins ...

  6. Python3.5源码分析-Dict概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的Dict对象 在生成d = {}和d['1'] ...

  7. Python3.5源码分析-List概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的List对象 list对象是一个变长对象,在运 ...

  8. Python3.5源码分析-内存管理

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的内存管理概述 python提供了对内存的垃圾收 ...

  9. Python3.5源码分析-内建模块builtins初始化

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...

最新文章

  1. Python 用户的三次登录机会
  2. 全球99家AI芯片公司,中国占15家!
  3. PostgreSQL client's startup packet different between logical and normal stream replication
  4. 服务器操作系统2008安装图解,IBM服务器windows2008操作系统安装步骤图文(13页)-原创力文档...
  5. 程序员法律考试笔记(1)-民法基本规定与调整范围
  6. boost::ratio_negate相关的测试程序
  7. 【原】TreeView+Checkbox级联操作(IE/FireFox测试通过)
  8. SylixOS下基于NUC970的NAND驱动
  9. 框架如何调用存储过程_如何在FastReport.Net中使用存储过程
  10. java socket编程聊天室_Java Socket通信之聊天室功能
  11. LeetCode 2197. 替换数组中的非互质数(栈)
  12. linux内核通俗理解,简洁明了!高手带你理解ARM-Linux的启动过程
  13. python经典程序实例-Python简单基础小程序的实例代码
  14. centos ipython tab键上下键不起作用
  15. kettle 下载地址
  16. pandas填充空数组_pandas | DataFrame基础运算以及空值填充
  17. 【统计】回归系数与相关系数的联系与区别
  18. 锐捷商通v6数据库服务器位置,热烈庆祝我校开通IPv6资源
  19. Redis面试完整版
  20. 笔记本电脑外接显示器无信号 其实是主板静电积压 完全可以不拆机放电

热门文章

  1. sublime前端插件推荐让sublime变成webstorm
  2. 基于 VPX 总线的工件台运动控制系统研究与开发-以光刻运动台为例(一)
  3. 人工智能的未来:机器学习和深度学习的发展趋势
  4. 图扑孪生工厂流水线组态图可视化
  5. Xshell怎么快速复制粘贴
  6. 爬虫之哔哩哔哩弹幕的获取
  7. Unity3D_向量(Vector3)数学基础
  8. 【转】VMWare+WinDbg搭建(驱动)调试环境
  9. mac 电脑连接不上github_告诉你电脑无线网络连接不上怎么办
  10. ProxmoxVE 之 使用thinstation利旧安装瘦客户端