• 理解 Python 的执行方式,与字节码 bytecode 玩耍 (上)


    这里有个博客讲 Python 内部机制,已经有一些中文翻译

    可能因为我用的Python 3.5,例子跑起来有些不一样。

    此外,我又查了其他一些参考资料,总结如下:

    Python 的执行方式

    先看一个比较详细的步骤分解:

    >>> a = "hello"

    输入这行代码之后,你一按回车,Python就会执行四步操作:

    1  lexing: 词法分析,就是把一个句子分解成 token。大致来说,就是用str.split()可以实现的功能。            

    2  parsing:解析,就是把这些 token 组装成一个逻辑结构。

    3  compiling:编译,把这个逻辑结构转化成一个或者多个code object (代码对象)

    4  interpreting:解释,执行每个code object 代表的代码。

    还有一种比较简单的说法是这样的:

    Python 程序的执行过程就是,它先把代码编译成 bytecode (字节码)指令,交给虚拟机,逐条执行 bytecode 指令。

    这两种说法基本上是一样的,只是存在一个code object 和 bytecode 的差异。那么它们之间存在怎样的关系呢?

    从操作上说,bytecode 可以在 code object 的属性中找到。

    分清function object、code object ,以及 bytecode

    >>> def double(a):
        return a*2
    
    >>> double
    <function double at 0x000001D8082E48C8>

    为什么粘贴到这里对齐会是这样?先不管了。

    从上面可以看到,定义一个函数之后,它就成了一个function object (函数对象)。只要不使用函数调用符号——也就是小括号——这个函数就不会执行。

    但是它已经被编译了,可以通过这个function object 的__code__ 属性找到它的 code object 

    >>> def double(a):
    return a*2

    >>> double
    <function double at 0x00000169C5F7FF28>

    >>> type (double)
    <class 'function'>


    >>> double.__code__  #找到double 函数对象的 code object

    <code object double at 0x00000169C5F36AE0, file "<pyshell#58>", line 1>


    >>> type(double.__code__)
    <class 'code'>

    最后一行可以看到, code object 的类型是 ‘code’ 

    前面说过,bytecodecode object 的一个属性的值。这个属性名为 co_code

    code object 的co_code属性里面,存放了一个字符串,它就是bytecode 序列:

    >>> double.__code__.co_code
    b'|x00x00dx01x00x14S'

    bytecode 是几个意思?

    >>> double.__code__.co_code
    b'|x00x00dx01x00x14S'
    >>> type(double.__code__.co_code)
    <class 'bytes'>
    >>> len(double.__code__.co_code)
    8

    它的类型是‘bytes’ ,长度是8。 你可能觉得奇怪,这个8是怎么数出来的?

    注意: Python 3 中 str 类型大致相当于 Python 2 中的unicode 类型,但是 Python 3 中 bytes 类型并不是Python 2 中的 str 类型改了个名字。

    bytes 是二进制序列,它的每个元素都是一个整数,值在0-255之间。

    >>> for i in double.__code__.co_code:
        print (i, end="    ")
    
        
    124    0    0    100    1    0    20    83   
    >>> double.__code__.co_code[-1] 83

    是不是正好8个元素? 第一个是124,最后一个是83

    >>> chr(124)
    '|'
    >>> chr(83)
    'S'

    这里是一个很让人迷惑的地方:为什么要把 '|' 、'S'这样的字符和 x00 这样的十六进制表示混在一起?这其实只是Python 在显示 bytes 类型的对象给你看的时候,会把ASCII 码范围内的十六进制元素直接用ASCII 字符显示出来。就像你们学校的成绩光荣榜上,前20名会显示照片,第21名之后只显示名字了。人家的显示方法就是这样。

    这段 bytecode 由8个整数组成,每个整数都有深刻的含义,不亚于昆汀的《八恶人》

    可能你已经猜到,其中必定有一些代表着指令,整数是一个字典中的键,我们需要的是这个字典中的值,也就是指令的名字

    这个字典就在文件opcode.py里。

    def_op('LOAD_CONST', 100)       # Index in const list
    def_op('BUILD_TUPLE', 102)      # Number of tuple items
    def_op('BUILD_LIST', 103)       # Number of list items
    def_op('BUILD_SET', 104)        # Number of set items

    好像……这些整数才是键值对里的值,我们需要的是键。

    有一个方法帮你找出值 (不要忘了先 import opcode)

    >>> opcode.opmap["LOAD_FAST"]
    124
    >>> opcode.opmap["RETURN_VALUE"]
    83

    找值好像没什么意思嘛,我们更需要的是找到键。

    >>> opcode.opname[83]
    'RETURN_VALUE'
    >>> opcode.opname[124]
    'LOAD_FAST'


    其实找键也不需要,Python 有个dis反汇编工具可以用 (不要忘了先 import dis)
    >>> dis.dis(double)
      2           0 LOAD_FAST                0 (a)
                  3 LOAD_CONST               1 (2)
                  6 BINARY_MULTIPLY
                  7 RETURN_VALUE

    再回头看看那8个整数(这就是 bytecode的意思 —— 用for循环把 bytecode 迭代一遍得到的数字,代表一个指令序列 )


    124 0 0 100 1 0 20 83
    偏移量  0     1    2     3     4    5    6     7 


    很明显,第2列 的0 3 6 7 ,就是每个字节的偏移量咯


    下篇在这里
  • 相关阅读:
    我的NopCommerce之旅(8): 路由分析
    我的NopCommerce之旅(7): 依赖注入(IOC/DI)
    我的NopCommerce之旅(6): 应用启动
    我的NopCommerce之旅(5): 缓存
    我的NopCommerce之旅(4): 定时任务之邮件
    我的NopCommerce之旅(3): 系统代码结构分析
    我的NopCommerce之旅(2): 系统环境及技术分析
    我的NopCommerce之旅(1): 系统综述
    关于CSS中部分浏览器兼容性问题的解决
    每天一个Js小demo之移动端全景图场景实现-全景装修图。主要知识点:css3,旋转角度检测
  • 原文地址:https://www.cnblogs.com/hello2764/p/5459758.html
Copyright © 2020-2023  润新知