Python是一种解释型语言。但是为什么我的源目录包含.pyc文件,这些文件被Windows标识为“编译的Python文件”?
.pyc
它们包含字节码,这是Python解释器将源代码编译到的。然后此代码由Python的虚拟机执行。
Python的留档这样解释定义:
Python是一种解释性语言,如与编译的相反,尽管区别可能是模糊的,因为字节码编译器的存在。这意味着源文件可以直接运行而不显式创建一个可执行文件,然后快跑。
这些是由Python解释器在导入.py文件时创建的,它们包含导入模块/程序的“编译字节码”,其想法是,如果.pyc比相应的.py文件更新,则从源代码到字节码的“翻译”(只需要完成一次)可以在后续import上跳过,从而加快启动速度。但它仍然被解释。
.py
import
我已经明白了Python是一种解释性语言…
这种流行的模因是不正确的,或者更确切地说,是建立在对(自然)语言水平的误解之上的:类似的错误是说“圣经是一本精装书”。
“圣经”是“一本书”,在某种意义上是一本书(实际的,物理对象确定为)书籍;被确定为“圣经副本”的书籍应该有一些基本的共同点(内容,尽管这些内容可以是不同的语言,不同的可接受的翻译,脚注和其他注释的水平)-然而,这些书完全可以在无数方面有所不同,这些方面被认为是基本的-装订的种类,装订的颜色,印刷中使用的字体,插图(如果有的话),宽可写的边距,数字和内置书签的种类,等等,等等。
圣经的典型印刷很有可能确实是精装装订-毕竟,这是一本典型的书,意味着要一遍又一遍地阅读,在几个地方添加书签,翻阅给定的章节和经文指针,等等,而好的精装装订可以使给定的副本在这种使用下持续更长时间。然而,这些是世俗的(实际的)问题,不能用来确定给定的实际书籍对象是否是圣经的副本:平装印刷是完全可能的!
类似地,Python是“一种语言”,在定义一个类语言实现的意义上,它必须在一些基本方面(语法,大多数语义学,除了那些明确允许它们不同的部分)都是相似的,但完全允许在几乎每个“实现”细节上有所不同——包括它们如何处理它们给出的源文件,它们是否将源文件编译为一些较低级别的形式(如果是,哪种形式——以及它们是否将这些编译形式保存到磁盘或其他地方),它们如何执行所述形式,等等。
经典的实现,CPython,通常被简称为“Python”——但它只是几个生产质量实现之一,与微软的IronPython(编译为CLR代码,即“. NET”)、Jython(编译为JVM代码)、PyPy(用Python本身编写,可以编译为各种“后端”形式,包括“即时”生成的机器语言)并列。它们都是Python(==“Python语言的实现”),就像许多表面上不同的书本对象都可以是圣经(==“圣经的副本”)一样。
如果您对CPython特别感兴趣:它将源文件编译成Python特定的低级形式(称为“字节码”),在需要时自动执行(当没有与源文件对应的字节码文件时,或者字节码文件比源文件旧或由不同的Python版本编译),通常将字节码文件保存到磁盘(以避免将来重新编译它们)。OTOH IronPython通常会编译为CLR代码(是否将它们保存到磁盘,具体取决于)和Jython到JVM代码(是否将它们保存到磁盘-如果确实保存它们,它将使用.class扩展名)。
.class
然后,这些较低级别的表单由适当的“虚拟机”(也称为“解释器”)执行——CPython VM、. Net运行时、JavaVM(又名JVM)。
因此,从这个意义上说(典型实现做什么),Python是一种“解释语言”,当且仅当C#和Java是:它们都有一个典型的实现策略,即首先生成字节码,然后通过VM/解释器执行它。
更有可能的焦点是编译过程有多“繁重”,缓慢和高仪式。CPython旨在尽可能快地编译,尽可能轻量级,尽可能少的仪式-编译器很少进行错误检查和优化,因此它可以在少量内存中快速运行,这反过来又让它在需要时自动透明地运行,大多数时候用户甚至不需要意识到正在进行编译。Java和C#通常在编译期间接受更多工作(因此不执行自动编译),以便更彻底地检查错误并执行更多优化。这是一个连续的灰度,而不是一个黑色或白色的情况,这将是完全武断的把一个阈值在一些给定的水平,并说,只有高于这个水平,你称之为“编译”!
Python(至少是最常见的实现)遵循将原始源代码编译为字节码,然后在虚拟机上解释字节码的模式。这意味着(同样是最常见的实现)既不是纯解释器也不是纯编译器。
然而,另一方面,编译过程大多是隐藏的--. pyc文件基本上被视为缓存;它们加快了速度,但你通常根本不需要意识到它们。它会根据文件时间/日期戳在必要时自动使它们无效并重新加载(重新编译源代码)。
我唯一一次看到这样的问题是,当编译的字节码文件不知何故获得了一个时间戳到未来,这意味着它看起来总是比源文件更新。由于它看起来更新,源文件从未重新编译过,所以无论你做了什么更改,它们都被忽略了…
没有解释语言这样的东西。是否使用解释器或编译器纯粹是实施的特征,与语言完全无关。
每语言可以通过解释器或编译器实现。绝大多数语言每种类型至少有一种实现。(例如,有C和C++的解释器,也有JavaScript,PHP,Perl,Python和Ruby的编译器。)此外,大多数现代语言实现实际上结合了解释器和编译器(甚至多个编译器)。
语言只是一组抽象的数学规则。解释器是语言的几种具体实现策略之一。这两者存在于完全不同的抽象级别上。如果英语是一种类型化语言,术语“解释型语言”将是一个类型错误。“Python是一种解释型语言”的陈述不仅仅是false(因为为false意味着该陈述甚至有意义,即使它是错误的),它只是简单地不构成感觉,因为语言可以从未定义为“解释型”。
特别是,如果你看看当前现有的Python实现,这些是他们正在使用的实现策略:
你可能会注意到该列表中的每个实现(加上一些我没有提到的其他实现,如tinypy、Shedkin或Husco)都有一个编译器。事实上,据我所知,目前还没有纯解释的Python实现,没有这样的实现计划,也从来没有这样的实现。
“解释语言”这个术语不仅没有意义,即使你把它解释为“具有解释实现的语言”,这显然是不正确的。不管是谁告诉你的,显然不知道他在说什么。
特别是,您看到的.pyc文件是由CPython、Stackless Python或Unladen Swallow生成的缓存字节码文件。
Python代码经历2个阶段。第一步将代码编译成. pyc文件,实际上是一个字节码。然后使用CPython解释器解释这个. pyc文件(字节码)。请参阅这个链接。这里的代码编译和执行过程很简单。
Python的*. py文件只是一个文本文件,您可以在其中编写几行代码。当您尝试使用“pythonfilename.py”执行此文件时
此命令调用Python虚拟机。Python虚拟机有2个组件:“编译器”和“解释器”。解释器不能直接读取*. py文件中的文本,因此此文本首先转换为针对PVM(不是硬件,而是PVM)的字节码。PVM执行此字节码。*. pyc文件也会生成,作为运行它的一部分,它会对shell或其他文件中的文件执行导入操作。
如果这个*. pyc文件已经生成,那么每次你运行/执行你的*. py文件时,系统直接加载你的*. pyc文件,不需要任何编译(这将节省你一些处理器的机器周期)。
生成*. pyc文件后,不需要*. py文件,除非您编辑它。
这是给初学者的,
Python在运行之前会自动将您的脚本编译为编译代码,即所谓的字节代码。
运行脚本不被视为导入,也不会创建. pyc。
例如,如果您有一个导入另一个模块xyz.py的脚本文件abc.py,当您运行abc.py时,将创建xyz.pyc,因为导入了xyz,但没有abc.pyc文件将是创建,因为没有导入abc.py。
如果您需要为未导入的模块创建. pyc文件,您可以使用py_compile和compileall模块。
py_compile
compileall
py_compile模块可以手动编译任何模块。一种方法是交互式地使用该模块中的py_compile.compile函数:
py_compile.compile
>>> import py_compile>>> py_compile.compile('abc.py')
这将把. pyc写入与abc.py相同的位置(您可以使用可选参数cfile覆盖它)。
cfile
您还可以使用compileall模块自动编译一个或多个目录中的所有文件。
python -m compileall
如果省略了目录名(本例中的当前目录),则模块编译在sys.path上找到的所有内容
sys.path
为了加快加载模块,Python将模块的编译内容缓存在. pyc中。
CPython将其源代码编译为“字节码”,并且出于性能原因,每当源文件发生更改时,它都会将此字节码缓存在文件系统上。这使得Python模块的加载速度更快,因为可以绕过编译阶段。当您的源文件foo.py时,CPython会将字节码缓存在源文件旁边的foo.pyc文件中。
在python3中,Python的导入机制被扩展为在每个Python包目录中的单个目录中写入和搜索字节码缓存文件。这个目录将被称为__pycache__。
这是一个流程图,描述了如何加载模块:
更多信息:
ref:PEP3147参考:“编译”的Python文件
tldr;它是源代码的转换代码,python VM解释以执行。
自下而上的理解:任何程序的最后阶段都是在硬件/机器上运行/执行程序的指令。所以这是执行前的阶段:
执行/在CPU上运行
将字节码转换为机器代码。
机器代码是转换的最后阶段。
说明在CPU上执行以机器代码的形式给出。机器代码可以是CPU的直接执行。
将源代码转换为字节码。
现在真正的情节。在执行这些阶段中的任何一个时,有两种方法:一次转换[或执行]代码(又名编译)和逐行转换[或执行]代码(又名解释)。
例如,我们可以将源代码编译为字节码,将字节码编译为机器代码,解释机器代码以供执行。
一些语言的实现跳过了第3阶段以提高效率,即将源代码编译成机器代码,然后解释机器代码以供执行。
一些实现跳过所有中间步骤并直接解释源代码以供执行。
现代语言通常涉及编译和解释。
例如,JAVA将源代码编译为字节码[这就是JAVA源代码的存储方式,作为字节码,[使用JVM]将字节码编译为机器代码,并解释机器代码以供执行。[因此,JVM对不同的操作系统的实现不同,但相同的JAVA源代码可以在安装了JVM的不同操作系统上执行。]
以Python为例,将源代码编译成字节码[通常作为. pyc文件伴随着. py源代码],将字节码编译成机器代码[由虚拟机如PVM完成,结果是可执行文件],解释机器代码/可执行文件以供执行。
我们什么时候可以说一种语言是解释或编译的?
因此,JAVA和Python是解释语言。
虚拟机是为了可移植性而引入的,在任何真实机器上拥有一个虚拟机将允许我们执行相同的源代码。大多数虚拟机使用的方法是编译,因此有些人会说这是编译语言。关于虚拟机的重要性,我们经常说这样的语言是编译和解释。
它将语言规范与语言实现区分开来:
任何编译器都由两个独立的部分组成:前端和后端。前端接收源代码,验证并将其翻译成中间码。之后,后端将其翻译成机器代码以在物理机或虚拟机中运行。解释器是一个编译器,但在这种情况下,它可以产生一种直接在虚拟机中执行中间代码的方式。要执行python代码,必须将代码转换为中间代码,然后将代码“组装”为字节码,可以存储在file.pyc中,因此每次运行时都无需编译程序的模块。您可以使用以下命令查看此组装的python代码:
from dis import disdef a(): pass dis(a)
任何人都可以构建Python语言中的静态二进制编译器,也可以构建C语言解释器。有一些工具(lex/yacc)可以简化和自动化构建编译器的过程。