【问题标题】:If Python is interpreted, what are .pyc files?如果 Python 被解释,什么是 .pyc 文件?
【发布时间】:2011-03-01 04:55:04
【问题描述】:

我被告知 Python 是一种解释型语言...
但是,当我查看我的 Python 源代码时,我看到了.pyc 文件,Windows 将其识别为“已编译的 Python 文件”。

这些是从哪里来的?

【问题讨论】:

标签: python compiled interpreted-language pyc


【解决方案1】:

它们包含byte code,这是 Python 解释器将源代码编译成的内容。这段代码然后由 Python 的虚拟机执行。

Python's documentation 是这样解释定义的:

Python 是一种解释型语言,因为 与编译的相反,尽管 区别可能是模糊的,因为 字节码编译器的存在。 这意味着源文件可以 不显式直接运行 创建一个可执行文件,然后 运行。

【讨论】:

  • 很有趣,谢谢。那么 Python 是否被认为是一种纯解释型语言?
  • @froadie:语言不是这样“解释”或“编译”的。 特定实现可以是解释器或编译器(或混合或 JIT 编译器)。
  • “已编译”的一个测试:它是否编译为实际的机器指令? Python 字节码不是机器指令,Java 'JVM' 指令也不是,因此这些语言都不是按该定义编译的。但是两者都“编译”为中间的“抽象机器”代码,并且都比通过或多或少直接解释源代码(这是老式 BASIC 所做的)来运行程序要快得多。
  • 学究起来,“编译”的意思是“翻译”。然后将 Python 编译 为字节码。 AFAIK,只有 Bash 是真正解释的,所有其他流行的“解释”语言都被编译成字节码。
  • 实际上,它们机器指令,而不是主机物理CPU的本机机器指令。因此,为什么我们称它为 VM ?就像汇编语言中的世界语一样。如今,我们甚至拥有用于虚构(但仍是模拟)CPU 的本机代码(Mojang 努力让孩子们感兴趣)。 Rexx 被(或可能)真正解释,BAT 和 CMD(和 DCL)被解释。
【解决方案2】:

这些是由 Python 解释器在导入 .py 文件时创建的,它们包含导入模块/程序的“编译字节码”,其想法是从源代码“翻译”到字节码(仅如果.pyc 比相应的.py 文件更新,则可以在随后的imports 上跳过,从而加快启动速度。但它仍然被解释。

【讨论】:

  • 是的。除了许多核心 Python 库是用 C 编写的。所以部分 python 运行解释,部分运行在 C 中。您可以对自己的性能敏感代码位执行相同操作。
  • 那么为什么执行 *.py 文件是常见的做法呢? *.pyc 执行不会比这更快吗?
  • @Ankur:如果当前有*.pyc文件,就会执行。如果没有,则编译 *.py 文件,然后执行编译后的版本。所以如果你已经有一个当前的 *.pyc 文件,调用 *.py 文件只需要一点点时间 - 只是比较两个文件的时间戳需要多长时间。
【解决方案3】:

我已经明白这一点 Python 是一种解释型语言...

这个流行的模因是不正确的,或者更确切地说,是建立在对(自然)语言水平的误解之上:类似的错误是说“圣经是一本精装书”。让我解释一下这个比喻......

“圣经”是“一本书”,是指一类(被识别为实际的物理对象)书籍;被认定为“圣经副本”的书籍应该有一些基本的共同点(内容,尽管即使是不同的语言,有不同的可接受的翻译,脚注和其他注释的水平)——然而,这些书是完全允许在许多被认为是基本的方面有所不同——装订种类、装订颜色、印刷中使用的字体、插图(如果有)、可写的页边距是否宽、内置书签的数量和种类等等。

很可能典型的版圣经确实是精装装订的——毕竟,这本书通常意味着要一遍又一遍地阅读,在几个地方加了书签,翻阅通过寻找给定的章节指针等,一个好的精装装订可以使给定的副本在这种使用下持续更长时间。然而,这些都是平凡(实际)的问题,不能用来确定给定的实际书籍对象是否是圣经的副本:平装本印刷是完全可能的!

同样,Python 是定义一类语言实现的“语言”,它们在某些基本方面都必须相似(语法、大多数语义,除了那些明确允许它们不同的部分)但完全允许在几乎每个“实现”细节上有所不同——包括它们如何处理给定的源文件,它们是否将源代码编译到较低的级别的表单(如果是,是哪种表单——以及他们是否将这些编译后的表单保存到磁盘或其他地方),它们如何执行所述表单,等等。

经典实现,CPython,通常简称为“Python”——但它只是几个生产质量实现之一,与微软的 IronPython(编译为 CLR 代码,即“.NET” )、Jython(编译成 JVM 代码)、PyPy(它本身是用 Python 编写的,可以编译成各种各样的“后端”形式,包括“即时”生成的机器语言)。它们都是 Python(=="Python 语言的实现"),就像许多表面上不同的书籍对象都可以是圣经(=="圣经副本")。

如果您特别对 CPython 感兴趣:它将源文件编译为特定于 Python 的低级形式(称为“字节码”),并在需要时自动编译(当没有与源文件对应的字节码文件时)文件,或者字节码文件比源文件旧或由不同的 Python 版本编译),通常将字节码文件保存到磁盘(以避免将来重新编译它们)。 OTOH IronPython 通常会编译为 CLR 代码(是否将它们保存到磁盘,具体取决于)和 Jython 到 JVM 代码(是否将它们保存到磁盘 - 如果确实保存它们,它将使用 .class 扩展名)。

这些较低级别的表单随后由适当的“虚拟机”(也称为“解释器”)执行——CPython VM、.Net 运行时、Java VM(也称为 JVM),视情况而定。

因此,从这个意义上说(典型实现是做什么的),Python 是一种“解释型语言”当且仅当 C# 和 Java 是:它们都有一个典型的实现策略,首先生成字节码,然后通过虚拟机/解释器。

更有可能关注的是编译过程的“繁重”、缓慢和隆重。 CPython 被设计为尽可能快地编译,尽可能轻量级,尽可能少的仪式——编译器几乎不做错误检查和优化,因此它可以在少量内存中快速运行,这反过来又让它在需要时自动和透明地运行,大多数时候用户甚至不需要知道正在进行编译。 Java 和 C# 通常在编译期间接受更多工作(因此不执行自动编译),以便更彻底地检查错误并执行更多优化。这是灰度的连续统一体,而不是黑色或白色的情况,将阈值设置在某个给定级别并说只有在该级别之上才称其为“编译”!-)

【讨论】:

  • 美丽的答案。只是对最后一段的小修正:Python 旨在尽可能快地编译(等等)。这次它真的是语言,它缺乏静态类型系统和东西。当人们谈论“解释”语言时,他们通常指的是“动态”语言。
  • @Elazar,实际上,不急于编译的 Python 的其他实现,例如 PyPy,设法进行了缺少静态类型所需的更彻底的分析,并产生了 Just-in-时间编译为机器代码(从而将长时间运行的程序加速很多倍)。
  • Cython 在哪里适合?你会认为它是一种不同的语言还是 Python 实现?此外,这种“解释”与编译的模因是否可能只是术语混淆,因为 Python 的 VM 通常被称为其“解释器”?调用 JVM 或 .NET 运行时解释器同样有效。它们都主要将字节码解释为 JIT 机器码(有一些缓存优化异常)
【解决方案4】:

Python(至少是它最常见的实现)遵循将原始源代码编译为字节码,然后在虚拟机上解释字节码的模式。这意味着(同样,最常见的实现)既不是纯解释器也不是纯编译器。

然而,另一方面,编译过程大部分是隐藏的——.pyc 文件基本上被视为缓存;它们加快了速度,但您通常根本不需要注意它们。它会根据文件时间/日期戳在必要时自动使它们无效并重新加载(重新编译源代码)。

我唯一一次看到这个问题是编译后的字节码文件不知何故获得了未来的时间戳,这意味着它看起来总是比源文件更新。由于它看起来更新,源文件从未重新编译,所以无论你做了什么更改,它们都被忽略了......

【讨论】:

    【解决方案5】:

    没有解释型语言。使用解释器还是编译器纯粹是实现的一个特点,与语言完全无关。

    每一种语言都可以由解释器或编译器来实现。绝大多数语言对每种类型都至少有一个实现。 (例如,C 和 C++ 有解释器,JavaScript、PHP、Perl、Python 和 Ruby 有编译器。)此外,大多数现代语言实现实际上结合了解释器和编译器(甚至多个编译器)。

    语言只是一组抽象的数学规则。解释器是一种语言的几种具体实现策略之一。这两者生活在完全不同的抽象级别上。如果英语是一种类型化语言,那么术语“解释语言”将是类型错误。 “Python 是一种解释性语言”这句话不仅是错误的(因为错误意味着该陈述甚至是有意义的,即使它是错误的),它只是简单地没有意义 sense,因为一种语言永远不能被定义为“被解释的”。

    特别是,如果您查看当前现有的 Python 实现,这些是它们正在使用的实现策略:

    • IronPython:编译为 DLR 树,然后 DLR 编译为 CIL 字节码。 CIL 字节码会发生什么取决于您在哪个 CLI VES 上运行,但 Microsoft .NET、GNU Portable.NET 和 Novell Mono 最终会将其编译为本机机器码。
    • Jython:解释 Python 源代码,直到它识别出热代码路径,然后将其编译为 JVML 字节码。 JVML 字节码会发生什么取决于您在哪个 JVM 上运行。 Maxine 将直接将其编译为未优化的本机代码,直到它识别出热代码路径,然后将其重新编译为优化的本机代码。 HotSpot 将首先解释 JVML 字节码,然后最终将热代码路径编译为优化的机器码。
    • PyPy:编译为 PyPy 字节码,然后由 PyPy VM 解释,直到它识别出热代码路径,然后根据您运行的平台编译为本机代码、JVML 字节码或 CIL 字节码。
    • CPython:编译为 CPython 字节码,然后对其进行解释。
    • Stackless Python:编译为 CPython 字节码,然后对其进行解释。
    • Unladen Swallow:编译为 CPython 字节码,然后对其进行解释,直到识别出热代码路径,然后将其编译为 LLVM IR,然后 LLVM 编译器将其编译为本机机器代码。
    • Cython:将 Python 代码编译为可移植的 C 代码,然后使用标准 C 编译器进行编译
    • Nuitka:将 Python 代码编译为与机器相关的 C++ 代码,然后使用标准 C 编译器进行编译

    您可能会注意到该列表中的每一个实现(加上我没有提到的其他一些实现,例如 tinypy、Shedskin 或 Psyco)都有一个编译器。事实上,据我所知,目前还没有纯粹解释的 Python 实现,没有计划过这样的实现,也从来没有这样的实现。

    “解释型语言”一词不仅没有意义,即使您将其解释为“具有解释型实现的语言”,这显然也不正确。谁告诉你的,显然不知道他在说什么。

    特别是,您看到的 .pyc 文件是由 CPython、Stackless Python 或 Unladen Swallow 生成的缓存字节码文件。

    【讨论】:

    • 像 MSBASIC 这样的老派基础没有中间形式。该程序直接从源代码形式(或近源代码形式,其中关键字由 1 字节标记表示,行 # 由 2 字节二进制整数表示,但其余只是 ASCII 的形式)解释。因此,实际上“goto”将花费不同的时间,具体取决于它必须搜索多少个源行来寻找匹配的目的地。像 a*b-2*cos(x) 这样的表达式在每次执行时都会被有效地重新解析。
    • @greggo:如果你想更老派,BASIC 的 original 版本是一个本地代码编译器。这应该证明“编译”或“解释”语言的概念是多么荒谬。
    • 感谢您解释各种 python 编译器/解释器的行为方式。我想知道是否有好的 Python 编译器可以生成高效的 C 或 JavaScript。这似乎非常可行,也许不是为了大众消费,但至少对于 Python 的一个合理子集。我也想知道 Cython 是什么。
    • @personal_cloud:我不太听从你的评论。是的,当然,我知道 Cython,但这与任何事情有什么关系?它不是 Python 的实现,它是一种完全不同的语言。另外,真的不难找到一个 JavaScript 的例子,事实上,所有目前存在的主流 JavaScript 实现都有编译器。最后,Jython 是 Python 的一个实现,就像 Python 的任何其他实现一样。它是一种语言在 Java 平台上的实现,就像 Java 平台上的任何其他语言实现一样。
    • @AnonCoward:有一个question on Stack Overflow 询问pyc 文件,这些文件是编译后的Python 字节码文件。这些编译后的 Python 字节码文件存在的事实,证明毫无疑问可以编译 Python。 Lisp 是具有EVAL 的原始语言,Lisp 已经编译了 50 多年。 Ruby 有eval,每个现有的 Ruby 实现都有一个编译器。 ECMAScript 有eval,每个现有的 ECMAScript 实现都有一个编译器。就像每一个 Python 实现一样。
    【解决方案6】:

    Python 代码经历了 2 个阶段。第一步将代码编译成 .pyc 文件,这实际上是一个字节码。然后使用 CPython 解释器解释这个 .pyc 文件(字节码)。请参考this 链接。这里用简单的术语解释了代码编译和执行的过程。

    【讨论】:

      【解决方案7】:

      Python 的 *.py 文件只是一个文本文件,您可以在其中编写几行代码。当您尝试使用“python filename.py”执行此文件时

      此命令调用 Python 虚拟机。 Python 虚拟机有 2 个组件:“编译器”和“解释器”。解释器无法直接读取 *.py 文件中的文本,因此该文本首先被转换为针对 PVM(不是硬件而是 PVM) 的字节码。 PVM 执行这个字节码。 *.pyc 文件也会生成,作为运行它的一部分,它会对 shell 或其他文件中的文件执行导入操作。

      如果这个 *.pyc 文件已经生成,那么每次你运行/执行你的 *.py 文件时,系统会直接加载你的 *.pyc 文件,它不需要任何编译(这将为你节省一些机器周期处理器)。

      一旦生成*.pyc文件,就不需要*.py文件了,除非你编辑它。

      【讨论】:

        【解决方案8】:

        这是为初学者准备的,

        Python 在运行之前自动将您的脚本编译为编译后的代码,即所谓的字节码。

        运行脚本不被视为导入,并且不会创建 .pyc。

        例如,如果您有一个脚本文件 abc.py 导入另一个模块 xyz.py,那么当您运行 abc.py , xyz.pyc 将在 xyz 被导入后创建,但不会创建 abc.pyc 文件,因为 abc.py 没有被导入。

        如果您需要为未导入的模块创建 .pyc 文件,可以使用 py_compilecompileall 模块。

        py_compile 模块可以手动编译任何模块。一种方法是在该模块中以交互方式使用py_compile.compile 函数:

        >>> import py_compile
        >>> py_compile.compile('abc.py')
        

        这会将 .pyc 写入与 abc.py 相同的位置(您可以使用可选参数 cfile 覆盖它)。

        您还可以使用 compileall 模块自动编译一个或多个目录中的所有文件。

        python -m compileall
        

        如果目录名(本例中为当前目录)被省略,模块将编译在sys.path 上找到的所有内容

        【讨论】:

        • 编译得到abc.py有什么好处?
        • @SaherAhwal 我能想到的一个好处是语法检查。
        【解决方案9】:

        为了加快加载模块,Python 将模块编译后的内容缓存在 .pyc 中。

        CPython 将其源代码编译为“字节码”,并且出于性能原因,每当源文件发生更改时,它都会将此字节码缓存在文件系统中。这使得 Python 模块的加载速度更快,因为可以绕过编译阶段。当您的源文件是 foo.py 时,CPython 会将字节码缓存在源文件旁边的 foo.pyc 文件中。

        在 python3 中,Python 的导入机制被扩展为在每个 Python 包目录内的单个目录中写入和搜索字节码缓存文件。这个目录将被称为 __pycache__ 。

        这是描述如何加载模块的流程图:

        更多信息:

        参考:PEP3147
        参考:“Compiled” Python files

        【讨论】:

        • 一旦 foo.py 在 pyc 中编译,然后在 foo.py 中进行一些更改,python 是如何处理重新编译它的?
        【解决方案10】:

        tldr;它是源代码的转换代码,python VM 解释执行。

        自下而上的理解:任何程序的最后阶段都是在硬件/机器上运行/执行程序的指令。所以这里是执行之前的阶段:

        1. 正在执行/在 CPU 上运行

        2. 将字节码转换为机器码

          • 机器码是转换的最后阶段。

          • 在 CPU 上执行的

            指令以机器码形式给出。机器代码可以由 CPU直接执行

        3. 字节码转换为机器码。

          • 字节码是一个中等阶段。为了效率,可以跳过它,但会牺牲便携性
        4. 源代码转换为字节码。

          • 源代码是人类可读的代码。这就是在 Pycharm 等 IDE(代码编辑器)上工作时使用的方法。

        现在是实际情节。执行这些阶段有两种方法:一次转换[或执行]代码(又名编译)和逐行转换[或执行]代码(又名解释)。

        • 例如,我们可以将源代码编译为字节码,将字节码编译为机器码,解释机器码以执行。

        • 为了提高效率,一些语言的实现会跳过第 3 阶段,即将源代码编译成机器码,然后解释机器码以执行。

        • 一些实现会跳过所有中间步骤,直接解释源代码以执行。

        • 现代语言通常涉及编译和解释

        • 例如,JAVA,将源代码编译为字节码[这是 JAVA 源代码的存储方式,作为字节码],将字节码编译为机器码[使用 JVM],并解释机器码以执行。 [因此JVM在不同操作系统上的实现方式不同,但相同的JAVA源代码可以在安装了JVM的不同操作系统上执行。]

        • 例如,Python,将源代码编译为字节码[通常在 .py 源代码附带的 .pyc 文件中找到],将字节码编译为机器码[由 PVM 等虚拟机和结果是一个可执行文件],解释机器代码/可执行文件以执行。

        • 我们什么时候可以说一种语言是解释型或编译型的?

          • 答案是研究执行中使用的方法。如果它一次执行所有机器代码(== 编译),那么它是一种编译语言。另一方面,如果它逐行执行机器代码 (==interpret),那么它就是一种解释型语言。
        • 因此,JAVA 和 Python 是解释型语言。

        • 第三阶段可能会出现混淆,即将字节码转换为机器码。这通常是使用称为虚拟机的软件完成的。之所以会出现这种混乱,是因为虚拟机的行为就像一台机器,但实际上并非如此!虚拟机是为了可移植性而引入的,在任何真实机器上都有一个虚拟机将允许我们执行相同的源代码。大多数虚拟机[即第三阶段]使用的方法是编译,因此有些人会说它是一种编译语言。对于 VM 的重要性,我们常说此类语言既可编译又可解释

        【讨论】:

          【解决方案11】:

          机器不懂英语或任何其他语言,它们只能理解字节码,它们必须被编译(例如,C/C++、Java)或解释(例如,Ruby、Python),.pyc 是一个字节码的缓存版本。 https://www.geeksforgeeks.org/difference-between-compiled-and-interpreted-language/ 这是编译语言与解释语言之间区别的快速阅读,TLDR 是解释语言,不需要您在运行时之前编译所有代码,因此大多数时候它们对输入等并不严格。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-20
            • 2018-08-23
            • 2011-04-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-07
            相关资源
            最近更新 更多