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可以得到字节码。

  • 相关阅读:
    SharePoint 2013 配置基于表单的身份认证
    SharePoint 2013 场解决方案包含第三方程序集
    SharePoint 2010 站点附加数据升级到SP2013
    SharePoint 2013 在母版页中插入WebPart
    SharePoint 2013 搭建负载均衡(NLB)
    SharePoint 部署解决方案Feature ID冲突
    SharePoint 2013 配置基于AD的Form认证
    SharePoint Server 2016 Update
    SharePoint 2013 为用户组自定义EventReceiver
    SharePoint 2013 JavaScript API 记录
  • 原文地址:https://www.cnblogs.com/chentianwei/p/12002967.html
Copyright © 2011-2022 走看看