zoukankan      html  css  js  c++  java
  • Python 字节码bytecode

    字节码bytecode

    python把源码文件编译成字节码文件,存放在__pycahe子目录内,用.pyc结尾。之后如果不再修改源码文件,运行时则使用*.pyc文件编译成机器码,这样不但运行速度快,而且支持多个操作系统。

    字节码,其实就是一种中间代码。

    前置知识

    在看字节码之前,先要了解一下code object。它们在datamodel.html中有介绍

    例子:

    >>> import dis
    >>> def hello():
    ...     print('Hello World!')
    ...
    >>> hello.__code__
    <code object hello at 0x1066683a0, file "<stdin>", line 1>

    __code__属性显示了编译后的函数体the compiled function body, 它是一个code object

    code object 代码对象

    code模块中的code类产生code对象。

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

    code object一个python的内部类型。即解释器内部使用的类型。也称为bytecode。

    它是python把源码编译成字节码时,创建的代码对象。

    code类有多个个data attributes(实例变量),其中:

    • co_consts  为一个包含字节码所使用的字面值的元组。如果code object代表一个函数,这个属性的第一项是函数的文档字符串。
    • co_varnames 为一个包含局部变量名称的元组。
    • 'co_names': <member 'co_names' of 'code' objects> 在函数体内引用的非本地name的tuple

    详细介绍见:https://docs.python.org/3/reference/datamodel.html#object.__repr__

    例子:

    >>> hello.__code__.co_consts
    (None, 'Hello World!')
     
    >>> hello.__code__.co_varnames
    ()
    >>> hello.__code__.co_names
    ('print',)

    dis模块

    使用上面的hello()函数。用dis.dis()方法, 它返回字节码操作的格式化试图。

    >>> dis.dis("hello()")
      1           0 LOAD_NAME                0 (hello)
                  2 CALL_FUNCTION            0
                  4 RETURN_VALUE
    >>>
    >>> dis.dis(hello)
      2           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_CONST               1 ('Hello World!')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
                  8 LOAD_CONST               0 (None)
                 10 RETURN_VALUE

    解释:

    dis 模块为 Python 字节码提供了一个 “反汇编”,它可以让你更容易地得到一个人类可读的版本,以及查找各种字节码指令。

    这是因为字节码是--非人类可读格式的字节。通过dis模块,就可以理解字节码。

    dis.dis(hello)会在函数hello被编译之前,返汇编它:

    • 例子中,做左边的数字2代表编译该字节码的源代码中的行号。
    • 左边列中的数字是字节码中指令的偏移量()/也称为索引,即指令xxx所在位置。⚠️见本文底部例子的解释。
    • 右边的数字是字节码指令的参数,数字后面实际的参数,⚠️见本文底部例子。

    本例子:

    •  LOAD_GLOBAL(namei)加载名称为 co_names[namei] 的全局对象推入栈顶。
      • 本例子:因为hello.__code__.co_names是('print', ),所以就是把print函数对象推入栈顶。
    • LOAD_CONST(consti) 将co_consts[consti]推入栈顶。
      • 本例子:将'Hello World!'这个字符串常量推入栈顶。
    • CALL_FUNCTION(argc)  Calls a callable object with positional arguments.
      • “计算栈”的顶端就包括所有的位置参数。从栈中弹出所有的参数和可调用对象,
      •  然后把参数传入对象并调用对象,最后把返回的值推入栈。
        •  ⚠️具体涉及到frame的生命周期和call stack的知识(尚未理解)
    • POP_TOP  移除栈顶(Top-of-Stack)的项目
    • LOAD_CONST(consti) 本例子是将None推入栈。因为没有指定函数的返回值,所以默认返回None。
    • RETURN_VALUE  把栈顶的值返回到the caller of the function。

    了解字节码的用处

    1. 了解字节码,可以帮助理解python的运行模型。通过python字节码,可以知道如何更好的写和优化源码。
    2. 理解字节码可以帮助你更好回答一些疑惑:比如,为什么 {} 比 dict() 快? 这是因为执行代码的过程有区别,通过dis.dis("{}")和dis.dis("dict()")就明白了。
    3. 理解字节码和Python如何运行它,就会理解面向栈的编程。有助开拓视野。

    延伸:

    • Python 虚拟机内幕,它是 Obi Ike-Nwosu 写的一本免费在线电子书,它深入 Python 解析器,解释了 Python 如何工作的细节。
    • 一个用 Python 编写的 Python 解析器,它是由 Allison Kaptur 写的一个教程,它是用 Python 构建的 Python 字节码解析器,并且它实现了运行 Python 字节码的全部构件。
    • 最后,CPython 解析器是一个开源软件,你可以在 GitHub 上阅读它。它在文件 Python/ceval.c 中实现了字节码解析器。这是 Python 3.6.4 发行版中那个文件的链接;字节码指令是由第 1266 行开始的 switch 语句来处理的。

    本文摘录:https://zhuanlan.zhihu.com/p/39259061

    知识点补充

    例子1

    >>> dis.opname[0x65]
    'LOAD_NAME'
    >>> dis.opname[101]
    'LOAD_NAME' 

    解释:

    dis.opname会获得一个list,索引101对应的是一个字符串"LOAD_NAME"。 

    >>> dis.opmap['LOAD_NAME']
    101

    dis.opmap得到一个dict, 映射操作名称到字节码mapping operation names to bytecodes, 索引'LOAD_NAME'的值是十进制数字101。

    101是一个十进制的字节码,对应的是"LOAD_NAME"

    例子2

    >>> x = dis.Bytecode(hello)
    >>> x
    Bytecode(<function hello at 0x106594b80>)
    >>> for instr in x:
    ...     print(instr.opname)
    ...
    LOAD_GLOBAL
    LOAD_CONST
    CALL_FUNCTION
    POP_TOP
    LOAD_CONST
    RETURN_VALUE

    解释:

    把函数hello分析为一个Bytecode对象。然后打印它内部的所有字节码的对应的人类可读的operation name。

    其实就是dis.dis(hello)中的第3列。

    >>> for instr in x:
    ...     print(instr.opcode)
    ...
    116
    100
    131
    1
    100
    83

    使用opcode打印出对应的字节码。116(这是十进制)对应LOAD_GLOBAL。

    >>> hello.__code__
    <code object hello at 0x1066683a0, file "<stdin>", line 1>
    >>> hello.__code__.co_code
    b'tx00dx01x83x01x01x00dx00Sx00'
    >>> list(hello.__code__.co_code)
    [116, 0, 100, 1, 131, 1, 1, 0, 100, 0, 83, 0]
    >>> dis.opname[116]
    'LOAD_GLOBAL'
    >>> dis.dis(hello)
      2           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_CONST               1 ('Hello World!')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
                  8 LOAD_CONST               0 (None)
                 10 RETURN_VALUE
    >>> dis.opname[1]
    'POP_TOP'

    由此可知dis.dis的第2列数字其实是把字节码list化后的索引。最右边那一列的数字其实就是字节码的参数。

    小结:

    dis.opname:得到字节码的含义的list表。

    dis.map: 一个dict,key是字节码的含义,value是十进制的字节码。 

    使用xx.__code__得到一个Code Object,用属性co_code可以得到字节码。

  • 相关阅读:
    如何阅读论文(译)
    Linux运维小知识
    认识Linux分区
    如何获取离线安装Chrome扩展程序的包
    Centos 7.4 下初探Zabbix安装
    尝试在Linux上部署Asp.net Core应用程序
    Centos 7.3下图文安装SQL Server
    Asp.net MVC Razor常见问题及解决方法
    轻量级高性能ORM框架:Dapper高级玩法
    Asp.net MVC 如何对所有用户输入的字符串字段做Trim处理
  • 原文地址:https://www.cnblogs.com/chentianwei/p/12002967.html
Copyright © 2011-2022 走看看