IT狗工作室

第2篇:CPython实现原理:字节码的执行

2020-05-08  本文已影响0人  铁甲万能狗

经过前一篇的了解,我们已经知道Python解释器实际执行的是编译后的字节码,本篇着重了解一下Python解释器执行字节码中内置操作码的流程,那么我们看一下下面示例

def fib(n):
    if n<2:
        return n
    current,next=0,1
    while n:
        current,next=next,current+next
        n-=1
    return current

因为fib是一个Python函数,而在Python中函数都是一个对象,当然也继承Python对象的内置属性 __ code__

from fib import fib
fib. __code__

__ code__ 属性返回fib函数的代码对象,而代码对象的co_code属性可以返回其字节码序列,我们知道代码对象包含了Python执行该含书所需要的所有信息:指令(操作码),常量和局部变量

我们先来看看co_consts属性,返回一个元组,它包含了含书主题中引用的所有文字或常量值

>>fib.__code__.co_consts
(None, 2, 0, 1, (0, 1))

常量2,0,1和一个包含0和1的元组,还有None,这个None的含义我们稍后再说,因为fib函数主体放在Python任何地方都没有包含文字,这样做的原因,如果Python正在执行我们的函数,并且没有到达任何显式return的语句情况下完成执行,它将返回None,所以Python需要确保载入None,并放入co_consts所指向的元组,因此任何Python函数都可以引用None,Python在编译时,它无法知道是否会到达任何显式的return语句,这种情况下Python解释器可以引用None并将其作为函数的返回值。

我们还有另一个属性叫co_varnames,该属性返回函数的所有局部变量的名称

>>fib.__code__.co_varnames
('n', 'current', 'next')

在其输出的元组中有n,current,next
而在一个co_names的属性中,即可以返回含书中引用的任何非本地名称,因为我们fib函数中没有调用其他def函数或Python的C级别函数,因此返回一个空的元组

>>fib.__code__.co_names
()

最后一个要介绍的是co_code,它是返回fib函数已编译后的字节序列

>>fib.__code__.co_code

下面的字节码序列就是Python3命令行中完成的,因此某些字节会打印成ASCII字符,因为Python shell环境下是以ASCII编码来显示字节对象的,因此需要警惕的是,我们不能将其视为字符串


ss8.png

之前说过字节码实质上就是指令(操作码)的序列,因此,我们需要了解其内容时,第一个字节对应的是什么操作码?Python函数有一个ASCII转换整数的形式

>>ord('|')
124

那么,124整个操作码,又如何表示我们人类能够理解的语义呢?可以使用dis模块中的opname数组

import dis
>>dis.opnames[124]
'LOAD_FAST'

124所标识的操作码就是LOAD_FAST,如此类推,opnames的每个索引值都是十进制的操作码整数其返回的字符串就是对应操作码所表示的含义。

解读字节码

更方便快捷的方式,可以使用dis模块中的dis函数

import dis
dis.dis(fib.__code__.co_code)

输出,下面打印的就是人类可理解的fib函数的字节码执行流程,解读这个反编译序列,需要一些方法,

从Python3.6开始,在字节码中,请谨记如下特性

ss8.png

值得一提的是,我们看到上图有尖括号,例如第4偏移量为12,第5行偏移量22,第7行偏移量为50,第8行偏移量52,这些指令都是在Python函数执行流程中跳转目标(Jump Target),这是Python告诉你,这些指令可能被此处正在执行的其他指令所跳转。

在上面示例的fib函数中,我们有一个while循环,每次我们必须转到循环的开头时,在判断当前循环中的代码是否执行都需要执行条件判断。对于字节码层面来说,带>>的指令行是其他指令的潜在跳转目标

Python VM中的栈

之前说过Python VM是基于栈的虚拟机,我们知道栈是跟踪函数调用的基本数据结构

更新中...

上一篇下一篇

猜你喜欢

热点阅读