设为首页 收藏本站
查看: 844|回复: 0

[经验分享] 从底层理解Python的执行

[复制链接]

尚未签到

发表于 2015-11-29 14:14:44 | 显示全部楼层 |阅读模式
  摘要:是否想在Python解释器的内部晃悠一圈?是不是想实现一个Python代码执行的追踪器?没有基础?不要怕,这篇文章让你初窥Python底层的奥妙。
  【编者按】下面博文将带你创建一个字节码级别的追踪API以追踪Python的一些内部机制,比如类似YIELDVALUE、YIELDFROM操作码的实现,推式构造列表(List Comprehensions)、生成器表达式(generator expressions)以及其他一些有趣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  
  

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

  现在,看一眼宏_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>  

  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)
  

  嗯,注释部分已经说明得很清楚了。通过一番查找,我们发现了可以用来从字典找固定字符串的函数_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();  
  }  
  

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



  • 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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-144974-1-1.html 上篇帖子: 一个用python实现的东方时尚(驾校)抢课程序 下篇帖子: selenium2+python学习笔记(二)Python编辑器之Sublime
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表