当我们通过命令行传入参数的方式调用 python 解释器去运行一个模块的时候,比如: $ python test.py 图2.1中所示的过程将开始进行。(当然这只是其中一种运行 Python 程序的方式比如也可以在交互模式下单行运行,对于交互模式,这里暂时不做讨论。) 基于Python3.7

  1. Python 可执行程序是一个用 C 语言编写的程序。当它被执行的时候,所发生的事情其实就和其他 C 语言程序(比如 Linux 内核或是一个简单的 hello world 程序)差不多。请花一点时间来理解一下,Python 可执行程序只是一个用来运行程序的程序,可以结合 C 与 汇编或者 LLVM 的关系来理解。当我们使用解释器运行一个模块的时候,最开始会运行一个与平台相关的标准初始化过程。
    所有讨论都假设在类 Unix 操作系统中,Windows 下会有些许不同。

  2. C 运行时环境会包办了所有的初始化操作,像是加载库、检查与设置环境变量等等。然后 python 可执行程序的 main 方法开始运行就像任何其他普通的 C 程序那样。

  3. python 的 main 函数位于 Programs/python.c文件中。 main 函数会调用位于 Modules/main.c中的 Py_Main 函数,它处理解释器的初始化过程,包括:解析命令行参数、设置程序的 flags、读取环境变量、运行 hook、哈希初始化等等。作为初始化过程的一部分, Python/pylifecycle.c中的 Py_Initialize 函数会被调用,它会初始化两个比较重要的数据结构:解释器状态与线程状态。
    看一眼解释器状态与线程状态的定义,它能给我们一些关于它们功能的信息。这两个数据结构由一些指向特定字段的指针组成,它们带有程序执行所需要的一些信息。首先我们来看解释器状态在源码中的定义(Include/pystate.h),以下是其中比较重要的部分:

    //解释器状态数据结构定义如下
    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;PyObject *builtins_copy;
    } PyInterpreterState;
    

    Python 程序员应该或多或少会对这些字段名字中的词有一些认识比如:sysdict,builtins,codec 等。

    • *next 字段为一个指向另一个解释器实例的引用(译注:多个解释器可以同时存在于一个进程当中,这个特性有望用于解决 CPython 的 GIL 问题,参考 PEP554)
    • *tstate_head 字段指向运行的主线程。当程序开启多线程时,解释器被它开启的所有线程所共享。线程状态随后将被讨论。
    • modules, modules_by_index, sysdict, builtins 以及 importlib 从它们的名字就能理解。它们都被定义为 PyObject 的实例,在 Python 虚拟机中 PyObject 为最基本的对象类型。
    • codec* 相关的字段持有用于定位、加载编码方式(encodings)的信息,对于字节解码(decoding bytes)非常重要。
  4. 程序的运行必须在一个线程之中进行,thread state structure 包含了一个线程执行 python code object 所需的信息,关于 thread state 定义的其中一部分代码如下(Include/pystate.h):

    //线程状态数据结构定义如下
    typedef struct _ts {struct _ts *prev;struct _ts *next;PyInterpreterState *interp;struct _frame *frame;int recursion_depth;char overflowed; /* The stack has overflowed. Allow 50 more callsto handle the runtime error. */char recursion_critical; /* The current calls must not causea stack overflow. */int stackcheck_counter;/* 'tracing' keeps track of the execution depth when tracing/profiling.This is to prevent the actual trace/profile code from being recorded inthe trace/profile. */int tracing;int use_tracing;Py_tracefunc c_profilefunc;Py_tracefunc c_tracefunc;PyObject *c_profileobj;PyObject *c_traceobj;/* The exception currently being raised */PyObject *curexc_type;PyObject *curexc_value;PyObject *curexc_traceback;PyObject *dict;  /* Stores per-thread state */int gilstate_counter;
    } PyThreadState;
    

    关于这两个数据结构,后面的章节中还会有进一步讨论。初始化过程还会设置一些重要的机制,比如基本的 stdio等。

  5. 一旦完成了所有的初始化,Py_Main 函数会调用 pymain_run_file函数,随后发生调用:

    PyRun_AnyFileExFlags ->
    PyRun_SimpleFileExFlags ->
    PyRun_FileExFlags ->
    PyParser_ASTFromFileObject
    

    在 PyRun_SimpleFileExFlags 函数调用中,main namespace 将被创建,文件内容将在其中被运行。它也将检查文件的 pyc 版本是否存在(pyc 文件是一个储存了已经被执行过的py文件编译后的代码(字节码)的文件)。当文件的 pyc 版本存在的时候,将会尝试以二进制形式读取并执行它。而当 pyc 文件不存在的时候,会顺着 PyRun_FileExFlags 向下调用, PyParser_ASTFromFileObject函数会接着调用 PyParser_ParseFileObject函数,它会读取被执行模块的内容并建立 parse tree,然后将 parse tree 传递给 PyAST_FromNodeObject根据 parse tree 创建 AST。(如果你阅读这些代码,你会看到很多的 Py_INCREF 和 Py_DECREF。这些内存管理函数会在后面详细讨论。CPython 通过引用计数管理对象的生命周期。当一个新的对象引用产生的时候,计数通过Py_INCREF增加,当一个引用离开作用域的时候会通过Py_DECREF减少。)

    AST 生成后会被传给 run_mod函数,这个函数会接着调用 PyAST_CompileObject 根据 AST 生成 code object。并且在调用 PyAST_CompileObject 生成字节码的时候会经过一个简单的 peephole optimizer 进行字节码优化。随后 run_mod 会调用 PyEval_EvalCode 随之产生函数调用:

    PyEval_EvalCode ->
    PyEval_EvalCodeEx ->
    _PyEval_EvalCodeWithName ->
    PyEval_EvalFrameEx
    
    • 在 PyEval_EvalFrameEx 中的解释器循环(interpreter loop)才会实际进行对 code object 的运行处理。它不只是将 code object 作为参数而是使用一个 frame object,它有一个字段用来保存到 code object 的引用。简单来说,解释器循环会不断读取储存在一个指令数组中的下一个指令进行执行,增加或者删除位于栈上的对象,直到没有新的指令或者中途发生异常中断了循环。
    • 头文件 Include/opcode.h 中包含了 python 虚拟机支持的 instruction/opcodes 清单。其实 opcodes 的概念很容易理解,在上面的例子中有4条 instruction:LOAD_FAST 将它的参数对应的值(这里对应x)加载到栈上。python 虚拟机是基于栈的 (stack based),也就是说 opcode 进行求值的对象以及求值得到的结果都会保存在栈上。BINARY_MULTIPLY opcode 会将前两条指令的结果从栈中弹出(pop),执行二进制乘法运算然后将结果放(push)回栈顶。RETURN_VALUE 指令会从栈中弹出设置为返回值并中断解释器循环。

    当所有的指令都被执行以后,Py_Main 将继续执行一些清理(clean up)过程。就像 Py_Initialize 所做的那样,Py_Finalize 会被调用完成一些清理任务,比如等待线程退出、调用 exit hooks、清理解释器分配的仍然被使用的内存等等。

上面我们在一个较高的层面上对 python 解释器如何执行程序进行了描述,但还有大量的细节没有被讨论,接下来的文章中我们将深入进去提供更多的细节信息。
参考视频
参考课程
参考文章

Cpython源码分析03(*)_简要总结下Cpython是如何执行python test.py相关推荐

  1. [pig4cloud框架源码分析] 03 - MyBatis中的sql语句日志打印

    文章目录 导读 pig4cloud框架配置 Mybatis Log Plugin 插件开启方式 插件说明 [TODO]源码分析 拦截器方案实现sql日志查看 参考资料 导读 使用MyBatis开发过程 ...

  2. react-redux源码分析及实现原型(下)

    上一次我们讲解了Provider.connect.selectorFactory.这次主要分析 connectAdvanced 这个核心API. react-redux源码分析及实现原型_上 conn ...

  3. android6.0源码分析之Camera API2.0下的Preview(预览)流程分析

    1.Camera2 preview的应用层流程分析 preview流程都是从startPreview开始的,所以来看startPreview方法的代码: <code class="hl ...

  4. android6.0源码分析之Camera API2.0下的初始化流程分析

    1.Camera2初始化的应用层流程分析 Camera2的初始化流程与Camera1.0有所区别,本文将就Camera2的内置应用来分析Camera2.0的初始化过程.Camera2.0首先启动的是C ...

  5. Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)

    (注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...

  6. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

  7. 云客Drupal源码分析之插件系统(下)

    以下内容仅是一个预览,完整内容请见文尾: 至此本系列对插件的介绍全部完成,涵盖了系统插件的所有知识 全文目录(全文10476字): 实例化插件 插件映射Plugin mapping 插件上下文   具 ...

  8. Cpython源码分析02_Python代码是怎么运行起来的

    目录 1.python代码运行时的入口 2.window与Linux入口出的区别 3.继续前进,生成_Py_Main对象,并做简单初始化 4.继续前进,我们来到了pymain_mian 5.pymai ...

  9. 【中级】【后台】 微信小程序 - 腾讯云 - wafer2 - PHP - DEMO - 003 - 源码分析 - 03 - 腾讯后台初始化 和 CodeIgniter

    本文原创,欢迎转载,但是,务必保持原文并且给出原文链接. 前言: 本节对Wafer 服务端 SDK 是腾讯云为微信小程序开发者提供的快速开发库,SDK 封装了以下功能供小程序开发者快速调用的源码的初始 ...

最新文章

  1. python基础知识面试题-干货满满--亲身经历的 Python 面试题
  2. 批量任务体现多线程的威力!
  3. Machine Learning week 8 quiz: Principal Component Analysis
  4. java equals 区别_Java中equals和==的区别
  5. android布局加色,android – 如何以编程方式将LinearLayout添加背景颜色,并将权重添加到另一个布局...
  6. 计算机的代表性产品,电脑展回顾 十款最具代表性存储产品
  7. 微信WeixinJSBridge API 屏蔽右上角分享等常用方法
  8. C++ STL sort 函数的用法(自定义排序函数)
  9. Lodop,前端自定义打印
  10. 小米允许安装未知来源不用sim卡_视频能独立通话的小米手表,会像小米手机一样好用吗?...
  11. 小程序和app究竟哪个好?
  12. 2011年IT行业薪资调查报告
  13. win查看服务器主板型号,Win10怎么看电脑主板型号?
  14. 计算基因组外显子长度
  15. 【CNN+VIT】LocalViT: Bringing Locality to Vision Transformers
  16. 经典的Embedding方法Word2vec
  17. Vue之导出xlsx
  18. [动态规划] 什么是动态规划
  19. 人工智能的三个层次:运算智能,感知智能,认知智能
  20. 从 Windows 过度到 Mac 必备快捷键对照表

热门文章

  1. 一分钟解答什么是CS资质认证
  2. Visual Studio Code+drawio扩展插件的安装和使用,免费的软件构图神器
  3. 【Python案例】——利用Django搭建一个钓鱼网站【轻松入门】
  4. 如何查看chrome浏览器插件位置
  5. 亲测解决:warning ....No license field 问题
  6. 基于深度学习的端到端通信系统模型
  7. 洛阳哪个中专学校计算机比较好,洛阳中职院校名单及排名 最好的中专学校有哪些...
  8. 牛客IOI周赛22-普及组 路线规划 查并集
  9. 苹果的系统默认字体是苹方字体吗
  10. LoRa硬件设备一共有几种产品形态?LoRa设备开发参考指南(十一)