• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • MsSql
  • Mysql
  • oracle
  • MariaDB
  • DB2
  • SQLite
  • PostgreSQL
  • MongoDB
  • Redis
  • Access
  • 数据库其它
  • sybase
  • HBase
您的位置:首页 > 数据库 >Mysql > 从底层简析Python程序的执行过程

从底层简析Python程序的执行过程

作者:hakril 字体:[增加 减小] 来源:互联网 时间:2017-05-11

hakril通过本文主要向大家介绍了python底层,底层驱动程序,底层程序,单片机底层程序,洗底层秘钥程序等相关知识,希望本文的分享对您有所帮助

最近我在学习 Python 的运行模型。我对 Python 的一些内部机制很是好奇,比如 Python 是怎么实现类似 YIELDVALUE、YIELDFROM 这样的操作码的;对于 递推式构造列表(List Comprehensions)、生成器表达式(generator expressions)以及其他一些有趣的 Python 特性是怎么编译的;从字节码的层面来看,当异常抛出的时候都发生了什么事情。翻阅 CPython 的代码对于解答这些问题当然是很有帮助的,但我仍然觉得以这样的方式来做的话对于理解字节码的执行和堆栈的变化还是缺少点什么。GDB 是个好选择,但是我懒,而且只想使用一些比较高阶的接口写点 Python 代码来完成这件事。

所以呢,我的目标就是创建一个字节码级别的追踪 API,类似 sys.setrace 所提供的那样,但相对而言会有更好的粒度。这充分锻炼了我编写 Python 实现的 C 代码的编码能力。我们所需要的有如下几项,在这篇文章中所用的 Python 版本为 3.5。

  •     一个新的 Cpython 解释器操作码
  •     一种将操作码注入到 Python 字节码的方法
  •     一些用于处理操作码的 Python 代码

一个新的 Cpython 操作码
新操作码:DEBUG_OP

这个新的操作码 DEBUG_OP 是我第一次尝试写 CPython 实现的 C 代码,我将尽可能的让它保持简单。 我们想要达成的目的是,当我们的操作码被执行的时候我能有一种方式来调用一些 Python 代码。同时,我们也想能够追踪一些与执行上下文有关的数据。我们的操作码会把这些信息当作参数传递给我们的回调函数。通过操作码能辨识出的有用信息如下:

  •     堆栈的内容
  •     执行 DEBUG_OP 的帧对象信息

所以呢,我们的操作码需要做的事情是:

  •     找到回调函数
  •     创建一个包含堆栈内容的列表
  •     调用回调函数,并将包含堆栈内容的列表和当前帧作为参数传递给它

听起来挺简单的,现在开始动手吧!声明:下面所有的解释说明和代码是经过了大量段错误调试之后总结得到的结论。首先要做的是给操作码定义一个名字和相应的值,因此我们需要在 Include/opcode.h中添加代码。

  /** My own comments begin by '**' **/ 
  /** From: Includes/opcode.h **/ 

  /* Instruction opcodes for compiled code */ 

  /** We just have to define our opcode with a free value 
    0 was the first one I found **/ 
  #define DEBUG_OP        0 

  #define POP_TOP         1 
  #define ROT_TWO         2 
  #define ROT_THREE        3 

</div>

这部分工作就完成了,现在我们去编写操作码真正干活的代码。
实现 DEBUG_OP

在考虑如何实现DEBUG_OP之前我们需要了解的是 DEBUG_OP 提供的接口将长什么样。 拥有一个可以调用其他代码的新操作码是相当酷眩的,但是究竟它将调用哪些代码捏?这个操作码如何找到回调函数的捏?我选择了一种最简单的方法:在帧的全局区域写死函数名。那么问题就变成了,我该怎么从字典中找到一个固定的 C 字符串?为了回答这个问题我们来看看在 Python 的 main loop 中使用到的和上下文管理相关的标识符 enter 和 exit。

我们可以看到这两标识符被使用在操作码 SETUP_WITH 中:

  /** From: Python/ceval.c **/ 
  TARGET(SETUP_WITH) { 
  _Py_IDENTIFIER(__exit__); 
  _Py_IDENTIFIER(__enter__); 
  PyObject *mgr = TOP(); 
  PyObject *exit = special_lookup(mgr, &PyId___exit__), *enter; 
  PyObject *res; 

</div>

现在,看一眼宏 _Py_IDENTIFIER 定义

/** From: Include/object.h **/

/********************* String Literals ****************************************/
/* This structure helps managing static strings. The basic usage goes like this:
  Instead of doing

    r = PyObject_CallMethod(o, "foo", "args", ...);

  do

    _Py_IDENTIFIER(foo);
    ...
    r = _PyObject_CallMethodId(o, &PyId_foo, "args", ...);

  PyId_foo is a static variable, either on block level or file level. On first
  usage, the string "foo" is interned, and the structures are linked. On interpreter
  shutdown, all strings are released (through _PyUnicode_ClearStaticStrings).

  Alternatively, _Py_static_string allows to choose the variable name.
  _PyUnicode_FromId returns a borrowed reference to the interned string.
  _PyObject_{Get,Set,Has}AttrId are __getattr__ versions using _Py_Identifier*.
*/
typedef struct _Py_Identifier {
  struct _Py_Identifier *next;
  const char* string;
  PyObject *object;
} _Py_Identifier;

#define _Py_static_string_init(value) { 0, value, 0 }
#define _Py_static_string(varname, value) static _Py_Identifier varname = _Py_static_string_init(value)
#define _Py_IDENTIFIER(varname) _Py_static_string(PyId_##varname, #varname)

</div>

嗯,注释部分已经说明得很清楚了。通过一番查找,我们发现了可以用来从字典找固定字符串的函数 _PyDict_GetItemId,所以我们操作码的查找部分的代码就是长这样滴。

   /** Our callback function will be named op_target **/ 
  PyObject *target = NULL; 
  _Py_IDENTIFIER(op_target); 
  target = _PyDict_GetItemId(f->f_globals, &PyId_op_target); 
  if (target == NULL && _PyErr_OCCURRED()) { 
    if (!PyErr_ExceptionMatches(PyExc_KeyError)) 
      goto error; 
    PyErr_Clear(); 
    DISPATCH(); 
  } 

</div>

为了方便理解,对这一段代码做一些说明:

  •     f 是当前的帧,f->f_globals 是它的全局区域
  •     如果我们没有找到 op_target,我们将会检查这个异常是不是 KeyError
  •     goto error; 是一种在 main loop 中抛出异常的方法
  •     PyErr_Clear() 抑制了当前异常的抛出,而 DISPATCH() 触发了下一个操作码的执行

下一步就是收集我们想要的堆栈信息。

  /** This code create a list with all the values on the current  stack **/ 
  PyObject *value = PyList_New(0); 
  for (i = 1 ; i <= STACK_LEVEL(); i++) { 
    tmp = PEEK(i); 
    if (tmp == NULL) { 
      tmp = Py_None; 
    } 
    PyList_Append(value, tmp); 
  } 

</div>

最后一步就是调用我们的回调函数!我们用 call_function 来搞定这件事,我们通过研究操作码 CALL_FUNCTION 的实现来学习怎么使用 call_function 。

  /** From: Python/ceval.c **/ 
  TARGET(CALL_FUNCTION) { 
    PyObject **sp, *res; 
    /** stack_pointer is a local of the main loop. 
      It's the pointer to the stacktop of our frame **/ 
    sp = stack_pointer; 
    res = call_function(&sp, oparg); 
    /** call_function handles the args it consummed on the stack   for us **/ 
    stack_pointer = sp; 
    PUSH(res); 
    /** Standard exception handling **/ 
    if (res == NULL) 
      goto error; 
    DISPATCH(); 
  } 

</div>

有了上面这些信息,我们终于可以捣鼓出一个操作码DEBUG_OP的草稿了:

  TARGET(DEBUG_OP) { 
    PyObject *value = NULL; 
    PyObject *target = NULL; 
    PyObject *res = NULL; 
    PyObject **sp = NULL; 
    PyObject *tmp; 
    int i; 
    _Py_IDENTIFIER(op_target); 

    target = _PyDict_GetItemId(f->f_globals, &PyId_op_target); 
    if (target == NULL && _PyErr_OCCURRED()) { 
      if (!PyErr_ExceptionMatches(PyExc_KeyError)) 
        goto error; 
      PyErr_Clear(); 
      DISPATCH(); 
    } 
    value = PyList_New(0); 
    Py_INCREF(target); 
    for (i = 1 ; i <= STACK_LEVEL(); i++) { 
      tmp = PEEK(i); 
      if (tmp == NULL) 
        tmp = Py_None; 
      PyList_Append(value, tmp); 
    } 

    PUSH(target); 
    PUSH(value); 
    Py_INCREF(f); 
    PUSH(f); 
    sp = stack_pointer; 
    res = call_function(&sp, 2); 
    stack_pointer = sp; 
    if (res == NULL) 
      goto error; 
    Py_DECREF(res); 
    DISPATCH(); 
  }

</div>

在编写 CPython 实现的 C 代码方面我确实没有什么经验,有可能我漏掉了些细节。如果您有什么建议还请您纠正,我期待您的反馈。

编译它,成了!

一切看起来很顺利,但是当我们尝试去

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • 从底层简析Python程序的执行过程

相关文章

  • 2018-12-05Linux下安装Mysql多实例作为数据备份服务器实现多主到一从多实例
  • 2017-05-11mysql登录遇到ERROR 1045问题解决方法
  • 2018-12-05封装mysql的JDBC该如何操作
  • 2018-12-05详解介绍MySQL5.6.31 winx64.zip安装配置的图文教程
  • 2018-12-05MySQL 事务表和非事务表
  • 2018-12-05MS Server和Oracle中对NULL处理的一些细节差异
  • 2018-12-05plsql与tsql的语法不同
  • 2018-12-05SQL数据操作基础(初级)4
  • 2018-12-05Mysql LONGTEXT 类型存储大文件(二进制也可以) (修改+调试+整理)
  • 2018-12-05Mysql命令行导入sql数据的代码

文章分类

  • MsSql
  • Mysql
  • oracle
  • MariaDB
  • DB2
  • SQLite
  • PostgreSQL
  • MongoDB
  • Redis
  • Access
  • 数据库其它
  • sybase
  • HBase

最近更新的内容

    • ip修改后orcale服务无法启动问题解决
    • oracle 更改数据库名的方法
    • Ubuntu 设置开放 MySQL 服务远程访问教程
    • Mysql大小写敏感的问题
    • MySQL优化之-集群搭建代码步骤详解(图)
    • MySQL中slave监控的延迟情况分析
    • MySQL 加密/压缩函数
    • 在SQL Server启动时自动执行存储过程。第1/2页
    • 卸载MySQL数据库的完整步骤(图)
    • 库名表名大小写问题与sqlserver兼容的启动配置方法

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有