解釋型語(yǔ)言,由Python解釋器把源文件編譯為pyc文件(字節(jié)碼文件), 然后讓Python虛擬機(jī)去運(yùn)行字節(jié)碼
2.2 編譯
執(zhí)行 python demo.py 后癣诱,將會(huì)啟動(dòng) Python 的解釋器乡翅,然后將 demo.py 編譯成一個(gè)字節(jié)碼對(duì)象 PyCodeObject狠怨。
有的人可能會(huì)很好奇项鬼,編譯的結(jié)果不應(yīng)是 pyc 文件嗎饵逐,就像 Java 的 class 文件,那為什么是一個(gè)對(duì)象呢笨奠,這里稍微解釋一下袭蝗。
在 Python 的世界中,一切都是對(duì)象般婆,函數(shù)也是對(duì)象到腥,類型也是對(duì)象,類也是對(duì)象(類屬于自定義的類型蔚袍,在 Python 2.2 之前乡范,int, dict 這些內(nèi)置類型與類是存在不同的配名,在之后才統(tǒng)一起來(lái),全部繼承自 object)晋辆,甚至連編譯出來(lái)的字節(jié)碼也是對(duì)象渠脉,.pyc 文件是字節(jié)碼對(duì)象(PyCodeObject)在硬盤上的表現(xiàn)形式。
在運(yùn)行期間瓶佳,編譯結(jié)果也就是 PyCodeObject 對(duì)象芋膘,只會(huì)存在于內(nèi)存中,而當(dāng)這個(gè)模塊的 Python 代碼執(zhí)行完后霸饲,就會(huì)將編譯結(jié)果保存到了 pyc 文件中为朋,這樣下次就不用編譯,直接加載到內(nèi)存中厚脉。pyc 文件只是 PyCodeObject 對(duì)象在硬盤上的表現(xiàn)形式习寸。
這個(gè) PyCodeObject 對(duì)象包含了 Python 源代碼中的字符串,常量值傻工,以及通過(guò)語(yǔ)法解析后編譯生成的字節(jié)碼指令霞溪。PyCodeObject 對(duì)象還會(huì)存儲(chǔ)這些字節(jié)碼指令與原始代碼行號(hào)的對(duì)應(yīng)關(guān)系,這樣當(dāng)出現(xiàn)異常時(shí)中捆,就能指明位于哪一行的代碼鸯匹。
2.3 pyc 文件
一個(gè) pyc 文件包含了三部分信息:Python 的 magic number、pyc 文件創(chuàng)建的時(shí)間信息泄伪,以及 PyCodeObject 對(duì)象忽你。
magic number 是 Python 定義的一個(gè)整數(shù)值。一般來(lái)說(shuō)臂容,不同版本的 Python 實(shí)現(xiàn)都會(huì)定義不同的 magic number科雳,這個(gè)值是用來(lái)保證 Python 兼容性的。比如要限制由低版本編譯的 pyc 文件不能讓高版本的 Python 程序來(lái)執(zhí)行脓杉,只需要檢查 magic number 不同就可以了糟秘。由于不同版本的 Python 定義的字節(jié)碼指令可能會(huì)不同,如果不做檢查球散,執(zhí)行的時(shí)候就可能出錯(cuò)尿赚。
下面所示的代碼可以來(lái)創(chuàng)建 pyc 文件,使用方法
python generate_pyc.py module_name
例如
python generate_pyc.py demo
[generate_pyc.pyc]
import imp
import sys
def generate_pyc(name):
fp, pathname, description = imp.find_module(name)
try:
imp.load_module(name, fp, pathname, description)
finally:
if fp:
fp.close()
if name == 'main':
generate_pyc(sys.argv[1])
2.4 字節(jié)碼指令
為什么 pyc 文件也稱作字節(jié)碼文件蕉堰?因?yàn)檫@些文件存儲(chǔ)的都是一些二進(jìn)制的字節(jié)數(shù)據(jù)凌净,而不是能讓人直觀查看的文本數(shù)據(jù)。
Python 標(biāo)準(zhǔn)庫(kù)提供了用來(lái)生成代碼對(duì)應(yīng)字節(jié)碼的工具 dis屋讶。dis 提供一個(gè)名為 dis 的方法冰寻,這個(gè)方法接收一個(gè) code 對(duì)象,然后會(huì)輸出 code 對(duì)象里的字節(jié)碼指令信息皿渗。
s = open('demo.py').read()
co = compile(s, 'demo.py', 'exec')
import dis
dis.dis(co)
執(zhí)行上面這段代碼可以輸出 demo.py 編譯后的字節(jié)碼指令
1 0 LOAD_CONST 0 (-1)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (foo)
9 STORE_NAME 0 (foo)
3 12 LOAD_CONST 2 (1)
15 LOAD_CONST 3 (u'python')
18 BUILD_LIST 2
21 STORE_NAME 1 (a)
4 24 LOAD_CONST 4 (u'a string')
27 STORE_NAME 1 (a)
6 30 LOAD_CONST 5 (<code object func at 00D97650, file "demo.py", line 6>)
33 MAKE_FUNCTION 0
36 STORE_NAME 2 (func)
11 39 LOAD_NAME 1 (a)
42 PRINT_ITEM
43 PRINT_NEWLINE
13 44 LOAD_NAME 3 (name)
47 LOAD_CONST 6 (u'main')
50 COMPARE_OP 2 (==)
53 POP_JUMP_IF_FALSE 82
14 56 LOAD_NAME 2 (func)
59 CALL_FUNCTION 0
62 POP_TOP
15 63 LOAD_NAME 0 (foo)
66 LOAD_ATTR 4 (add)
69 LOAD_CONST 2 (1)
72 LOAD_CONST 7 (2)
75 CALL_FUNCTION 2
78 POP_TOP
79 JUMP_FORWARD 0 (to 82)
>> 82 LOAD_CONST 1 (None)
85 RETURN_VALUE
2.5 Python 虛擬機(jī)
demo.py 被編譯后斩芭,接下來(lái)的工作就交由 Python 虛擬機(jī)來(lái)執(zhí)行字節(jié)碼指令了轻腺。Python 虛擬機(jī)會(huì)從編譯得到的 PyCodeObject 對(duì)象中依次讀入每一條字節(jié)碼指令,并在當(dāng)前的上下文環(huán)境中執(zhí)行這條字節(jié)碼指令划乖。我們的程序就是通過(guò)這樣循環(huán)往復(fù)的過(guò)程才得以執(zhí)行贬养。