如果解释了Python,那么什么是.pyc文件?


1083

我已经了解Python是一种解释语言...
但是,当我看我的 Python源代码时,我看到的.pyc是Windows标识为“编译的Python文件”的文件。

这些从哪里来?


3
理由请参见stackoverflow.com/questions/11433579/…。一言以蔽之:速度。
user7610



这是否意味着python就像Java一样,也具有“编写一次,可以在任何地方运行”的功能?
Mrak Vladar '19

2
@MrakVladar甚至Java都是“只要编写一次,就可以在任何有JVM的地方运行”。Python也不例外。它可以“在拥有Python虚拟机的任何地方运行”。最大的不同是,大多数Python实现将编译器和解释器组合为一个可执行文件,而不是像java和那样将它们分开javac
chepner

Answers:


660

它们包含字节码,这是Python解释器将源代码编译到的字节码。然后,此代码由Python的虚拟机执行。

Python的文档解释了这样的定义:

Python是一种解释型语言,与编译型语言相反,尽管由于字节码编译器的存在,两者之间的区别可能很模糊。这意味着可以直接运行源文件,而无需显式创建然后运行的可执行文件。


10
有趣,谢谢。那么Python是否被视为一种纯解释性语言?
froadie 2010年

194
@froadie:这样的语言不会被“解释”或“编译”。一个特定的实现可以是解释器或编译器(或混合或JIT编译器)。
Joachim Sauer 2010年

30
一种“编译”测试:是否已按照实际的机器指令进行编译?Python字节码不是机器指令,也不是Java'JVM'指令,因此这些语言都不会被该定义编译。但是两者都“编译”为中间的“抽象机器”代码,并且比通过运行或多或少直接解释源代码(这是老式的BASIC所做的)来运行该程序要快得多,快得多。
greggo 2013年

20
要学究,“编译”是指“翻译”。然后将Python 编译为字节码。AFAIK,只有Bash 真正被 解释,所有其他流行的“解释”语言都被编译成字节码。
bfontaine 2014年

13
实际上,它们机器指令,而不是主机物理CPU的机机器指令。因此,为什么我们称它为VM?就像世界语一样,它也是汇编语言。如今,我们甚至拥有虚构的(但仍是仿真的)CPU的本机代码(Mojang努力使孩子们感兴趣)。Rexx被(或可能被)真正地解释,并且BAT和CMD(和DCL)被解释。
mckenzm 2014年

994

我已经了解Python是一种解释语言...

这种流行的模因是不正确的,或者是基于对(自然)语言水平的误解造成的:类似的错误是说“圣经是一本精装书”。让我解释一下这个比喻...

“圣经”是“一书的”,即一个感(标识为实际的物理对象)的书籍; 被认为是“圣经副本”的书应该具有基本的共同点(内容,尽管即使这些书可以使用不同的语言,具有不同的可接受的翻译,脚注和其他注释的级别),但是这些书是完全可以在被认为是基础的许多方面进行区别-装订类型,装订颜色,打印中使用的字体,插图(如有),可写边距宽,是否内置书签,数量和种类, 等等等等。

很有可能典型的圣经印刷确实是精装书本-毕竟,这是一本书,通常一遍又一遍地读,在几个地方加上书签,通过寻找给定的章节指针来翻阅等等,而良好的精装书装订可以使给定的副本在这种使用下的使用寿命更长。但是,这些都是平凡的(实用的)问题,不能用来确定给定的实际书本对象是否是圣经的副本:平装本完全可以印刷!

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

经典实现CPython通常简称为“ Python”,但是它只是几种生产质量实现,与Microsoft的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是:它们都具有一种典型的实现策略,即首先生成字节码,然后通过VM /解释器执行字节码。 。

更有可能的重点是编译过程的“繁重”,缓慢和高仪式性。CPython旨在尽可能快地编译,尽可能轻量级,尽可能少地执行仪式-编译器几乎不执行错误检查和优化,因此它可以快速运行并占用少量内存,这反过来又使它可以运行可以在任何需要的时候自动透明地运行,而用户甚至在大多数情况下都不需要知道正在进行编译。Java和C#通常在编译期间接受更多工作(因此不执行自动编译),以便更彻底地检查错误并执行更多优化。这是灰度的连续体,而不是黑白情况,


2
漂亮的答案。只是对最后一段的一小部分修正:Python旨在尽可能快地进行编译(等)。这次确实是语言,因为它缺少静态类型系统和东西。人们谈论“解释”语言时,通常指“动态”语言。
Elazar 2015年

1
@Elazar,实际上,不急着编译的其他Python实现(例如PyPy)能够进行由于缺少静态类型而需要进行的更彻底的分析,并能及时对机器代码进行编译(从而加快了速度)多次运行长时间运行的程序)。
Alex Martelli 2015年

Cython放在哪里?您会认为它是另一种语言还是Python实现?另外,由于Python的VM通常被称为“解释器”,因此这种“解释”与“编译”的模因可能只是术语混淆吗?调用JVM或.NET运行时解释器同样有效。他们俩都将字节码解释为JIT机器码(有一些缓存优化例外)
Davos

181

没有所谓的解释语言。使用解释器还是编译器纯粹是实现的特征,与该语言绝对无关。

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

语言只是一组抽象的数学规则。解释器是一种语言的几种具体实现策略之一。这两个人生活在完全不同的抽象级别上。如果英语是一种打字语言,则术语“解释语言”将是一种打字错误。语句“ Python是一种解释性语言”不仅是错误的(因为如果错误,则意味着该语句甚至是有意义的,即使它是错误的),它只是简单的没有意义,因为一种语言永远无法将定义为“解释。”

特别是,如果您查看当前现有的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字节码,然后对其进行解释。
  • 无堆栈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生成的缓存字节码文件。


5
诸如MSBASIC之类的老式基础没有中间形式。程序是直接从源代码形式(或接近源代码,用1字节的令牌表示关键字,用2字节的二进制整数表示行号,而其余只是ASCII)表示的。因此,实际上,“ goto”将花费不同的时间,具体取决于它必须通过查找匹配的目的地来搜索多少源代码行。像a * b-2 * cos(x)这样的表达式在每次执行时都会被有效地重新解析。
greggo 2013年

4
@greggo:而且,如果您想流连忘返,则BASIC 的原始版本是本机代码编译器。这应该证明“编译”或“解释”语言的概念多么荒谬。
约尔格W¯¯米塔格

感谢您解释各种python编译器/解释器的行为。我想知道是否有好的Python编译器可以生成有效的C或JavaScript。这似乎非常可行,也许不是为了大量使用,但至少对于合理的Python子集而言。我也想知道Cython是什么。
personal_cloud

Cython在2009年的SciPy中被提及,但是我可以原谅您在2010年还不了解它(在这里我只是在2017年才刚刚了解它)。仍然我们应该找到一个JavaScript示例... Jython对我来说毫无意义(Java到2009年就已经死了吗?嗯,也许不是... C ++ Boost当时还不是很好)
personal_cloud

1
@personal_cloud:我不太听您的评论。是的,我当然了解Cython,但这与什么有什么关系?它不是Python的实现,而是一种完全不同的语言。而且,找到一个JavaScript示例确实不难,实际上,当前所有现有的主流JavaScript实现都具有编译器。最后,就像任何其他Python实现一样,Jython是Python的实现。就像Java平台上的任何其他语言实现一样,它是Java平台上的一种语言实现。
约尔格W¯¯米塔格

61

它们是由Python解释器在.py导入文件时创建的,它们包含导入的模块/程序的“已编译字节码”,其想法是从源代码“转换”为字节码(只需要执行一次)。import如果s .pyc比相应.py文件新,则可以在后续s 上跳过,从而加快启动速度。但是它仍然被解释。


10
真正。除了许多用C编写的核心Python库外,因此python的某些部分运行解释为,部分运行在C中。您可以对自己的性能敏感代码执行相同的操作。
bwawok 2010年

44

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

CPython将其源代码编译为“字节代码”,并且出于性能方面的考虑,只要源文件发生更改,它都会在文件系统上缓存该字节代码。由于可以绕过编译阶段,因此可以更快地加载Python模块。当您的源文件是foo.py时,CPython将字节代码缓存在源代码旁边的foo.pyc文件中。

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

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

在此处输入图片说明

欲获得更多信息:

参考:PEP3147
参考:“已编译” Python文件


38

这是给初学者的,

在运行脚本之前,Python会自动将脚本编译为已编译的代码,即字节代码。

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

举例来说,如果你有一个脚本文件abc.py是进口的另一个模块xyz.py,当你运行abc.pyxyz.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


6
编译得到abc.py有什么好处?
Saher Ahwal

@SaherAhwal我能想到的一个好处是语法检查。
易宝

20

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

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

我唯一一次看到的问题是,经过编译的字节码文件以某种方式获得了未来的时间戳,这意味着它看起来总是比源文件新。由于它看起来较新,因此从未重新编译源文件,因此无论您进行了什么更改,它们都将被忽略...


12

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

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

如果此* .pyc文件已经生成,则下次您运行/执行* .py文件时,系统会直接加载* .pyc文件,而无需进行任何编译(这将为您节省一些处理器的机器周期)。

生成* .pyc文件后,除非您进行编辑,否则不需要* .py文件。


7

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.