首页 > 编程知识 正文

python解释器原理(python有编译器吗)

时间:2023-05-05 00:11:12 阅读:87 作者:266

编译自:https://opensource.com/Article/18/4/Introduction-Python-字节码

作者:詹姆斯贝内特

翻译:qhwdw

了解什么是Python字节码,Python如何使用它来执行您的代码,以及它如何帮助您。

如果你曾经写过Python,或者刚用过Python,可能经常会看到Python源代码文件——,文件名以。皮。您可能见过其他类型的文件,例如以结尾的文件。pyc,或者你可能听说过它们是Python字节码文件。(这些在Python 3中可能不容易看到——,因为它们与您的不在同一个目录中。py文件,它们位于一个名为__pycache__)的子目录中或者您听说过这是一种节省时间的方法,可以避免每次Python运行时都要重新解析源代码。

但是,除了“哦,这是Python字节码”,你知道这些文件能做什么吗?以及Python如何使用它们?

如果你不知道,那你就走运了!今天,我将向您展示什么是Python字节码,Python如何使用它来运行您的代码,以及它如何帮助您。

Python 如何工作

Python经常作为解释性语言——引入。原因之一是程序运行时,你的源代码被转换成了CPU的原生指令3354,但这个观点只是部分正确。Python和大多数解释语言一样,确实将源代码编译成一组虚拟机指令,并且针对相应的虚拟机实现了Python解释器。这种中间格式称为“字节码”。

因此,这些。pyc文件由Python悄悄地留下,以使它们运行“更快”或“优化”版本的源代码。它们是字节码指令,您的程序在Python虚拟机上运行。

我们来看一个例子。这里是经典节目《你好,世界》用Python编写。

def hello()

打印(‘你好,世界!’)

以下是转换后的字节码(转换为人类可读格式):

2 0加载_全局0(打印)

2 LOAD_CONST 1('你好,世界!')

4呼叫功能1

如果您输入hello()函数,然后使用CPython解释器运行它,那么上面列出的内容就是Python运行的内容。这可能看起来有点奇怪,所以让我们更多地了解它做了什么。

Python 虚拟机内幕

CPython使用基于堆栈的虚拟机。也就是说,它完全面向堆栈数据结构(您可以将一些东西“推”到堆栈顶部,或者从堆栈顶部“弹出”一些东西)。

CPython使用三种类型的堆栈:

调用堆栈。这是运行Python程序的主要结构。它为每个当前活动的函数调用使用——个“框架”,堆栈的底部是程序的入口点。每个函数调用都会向调用堆栈推一个新的帧,每当函数调用返回时,这个帧就会被销毁。在每一帧中,都有一个评估堆栈(也称为数据堆栈)。这个栈是Python函数运行的地方。运行的大多数Python代码都是由推入这个堆栈的东西组成的,操作它们,然后在它们返回时销毁它们。在每个帧中,还有一个块堆栈。Python使用它来跟踪某些类型的控制结构:循环、try/except块和with块,所有这些都被推入块堆栈。当细长的眼睛离开这些控制结构时,块堆栈被破坏。这将有助于Python知道在任何给定时刻哪个块是活动的,例如,continue或break语句可能会影响正确的块。大多数Python字节码指令都在当前调用堆栈框架的计算堆栈上操作,尽管有一些指令可以做其他事情(例如跳转到指定的指令或操作块堆栈)。

为了更好地理解,假设我们有一些调用函数的代码,比如:my_function(my_variable,2)。Python被转换成一系列字节码指令:

LOAD_NAME指令查找函数对象my_function,然后将其推到计算堆栈的顶部。另一个LOAD_NAME指令查找变量my_variable。然后将其推到计算堆栈的顶部。LOAD_CONST指令将一个实整数值2推到计算堆栈的顶部。CALL_FUNCTION指令将有两个参数,这意味着Python需要从栈顶弹出两个位置参数。然后在上面调用函数,同时弹出(对于函数涉及的关键字参数,使用另一条不同的指令—— CALL_FUNCTION_KW,但是

使用的操作原则类似,以及第三个指令 —— CALL_FUNCTION_EX,它适用于函数调用涉及到参数使用 * 或 ** 操作符的情况)。一旦 Python 拥有了这些之后,它将在调用栈上分配一个新帧,填充到函数调用的本地变量上,然后,运行那个帧内的 my_function 字节码。运行完成后,这个帧将被调用栈销毁,而在最初的帧内,my_function 的返回值将被推入到计算栈的顶部。

访问和理解 Python 字节码

如果你想玩转字节码,那么,Python 标准库中的 dis 模块将对你有非常大的帮助;dis 模块为 Python 字节码提供了一个 “反汇编”,它可以让你更容易地得到一个人类可读的版本,以及查找各种字节码指令。 dis 模块的文档 可以让你遍历它的内容,并且提供一个字节码指令能够做什么和有什么样的参数的完整清单。

例如,获取上面的 hello() 函数的列表,可以在一个 Python 解析器中输入如下内容,然后运行它:

import dis

dis.dis(hello)

函数 dis.dis() 将反汇编一个函数、方法、类、模块、编译过的 Python 代码对象、或者字符串包含的源代码,以及显示出一个人类可读的版本。dis 模块中另一个方便的功能是 distb()。你可以给它传递一个 Python 追溯对象,或者在发生预期外情况时调用它,然后它将在发生预期外情况时反汇编调用栈上最顶端的函数,并显示它的字节码,以及插入一个指向到引发意外情况的指令的指针。

它也可以用于查看 Python 为每个函数构建的编译后的代码对象,因为运行一个函数将会用到这些代码对象的属性。这里有一个查看 hello() 函数的示例:

>>> hello.__code__

<code object hello at 0x104e46930, file "<stdin>", line 1>

>>> hello.__code__.co_consts

(None, 'Hello, World!')

>>> hello.__code__.co_varnames

()

>>> hello.__code__.co_names

('print',)

代码对象在函数中可以以属性 __code__ 来访问,并且携带了一些重要的属性:

co_consts 是存在于函数体内的任意实数的元组co_varnames 是函数体内使用的包含任意本地变量名字的元组co_names 是在函数体内引用的任意非本地名字的元组

许多字节码指令 —— 尤其是那些推入到栈中的加载值,或者在变量和属性中的存储值 —— 在这些元组中的索引作为它们参数。

因此,现在我们能够理解 hello() 函数中所列出的字节码:

LOAD_GLOBAL 0:告诉 Python 通过 co_names (它是 print 函数)的索引 0 上的名字去查找它指向的全局对象,然后将它推入到计算栈LOAD_CONST 1:带入 co_consts 在索引 1 上的字面值,并将它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因为 Python 函数调用有一个隐式的返回值 None,如果没有显式的返回表达式,就返回这个隐式的值 )。CALL_FUNCTION 1:告诉 Python 去调用一个函数;它需要从栈中弹出一个位置参数,然后,新的栈顶将被函数调用。

“原始的” 字节码 —— 是非人类可读格式的字节 —— 也可以在代码对象上作为 co_code 属性可用。如果你有兴趣尝试手工反汇编一个函数时,你可以从它们的十进制字节值中,使用列出 dis.opname 的方式去查看字节码指令的名字。

字节码的用处

现在,你已经了解的足够多了,你可能会想 “OK,我认为它很酷,但是知道这些有什么实际价值呢?”由于对它很好奇,我们去了解它,但是除了好奇之外,Python 字节码在几个方面还是非常有用的。

首先,理解 Python 的运行模型可以帮你更好地理解你的代码。人们都开玩笑说,C 是一种 “可移植汇编器”,你可以很好地猜测出一段 C 代码转换成什么样的机器指令。理解 Python 字节码之后,你在使用 Python 时也具备同样的能力 —— 如果你能预料到你的 Python 源代码将被转换成什么样的字节码,那么你可以知道如何更好地写和优化 Python 源代码。

第二,理解字节码可以帮你更好地回答有关 Python 的问题。比如,我经常看到一些 Python 新手困惑为什么某些结构比其它结构运行的更快(比如,为什么 {} 比 dict() 快)。知道如何去访问和阅读 Python 字节码将让你很容易回答这样的问题(尝试对比一下: dis.dis("{}") 与 dis.dis("dict()") 就会明白)。

最后,理解字节码和 Python 如何运行它,为 Python 程序员不经常使用的一种特定的编程方式提供了有用的视角:面向栈的编程。如果你以前从来没有使用过像 FORTH 或 Fator 这样的面向栈的编程语言,它们可能有些古老,但是,如果你不熟悉这种方法,学习有关 Python 字节码的知识,以及理解面向栈的编程模型是如何工作的,将有助你开拓你的编程视野。

延伸阅读

如果你想进一步了解有关 Python 字节码、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 语句来处理的。

学习更多内容,参与到 James Bennett 的演讲, 有关字节的知识:理解 Python 字节码 ,将在 PyCon Cleveland 2018 召开。


via: https://opensource.com/article/18/4/introduction-python-bytecode

作者: James Bennett 选题: lujun9972 译者: qhwdw 校对: wxy

本文由 LCTT 原创编译, Linux中国 荣誉推出

点击“了解更多”可访问文内链接

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。