已编译脚本的字节码因编译方式而异
Byte code of a compiled script differs based on how it was compiled
当天早些时候,我对文档字符串和 dis
模块进行了大量试验,遇到了一些我似乎无法找到答案的问题。
首先,我创建一个包含以下内容的文件 test.py
:
def foo():
pass
只有这个,没有别的。
然后我打开一个解释器来观察程序的字节码。你可以这样得到它:
code = compile(open('test.py').read(), '', 'exec')
第一个参数是字符串形式的代码,第二个参数用于调试目的(留空是O.K。)而第三个是模式。 single
和 exec
我都试过了。结果是一样的。
之后,你可以用dis
反编译字节码。
>>> import dis
>>> dis.dis(code)
字节码输出是这样的:
1 0 LOAD_CONST 0 (<code object foo at 0x10a25e8b0, file "", line 1>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
9 LOAD_CONST 1 (None)
12 RETURN_VALUE
有道理,这么简单的脚本。这也是有道理的。
然后我尝试像这样通过命令行编译它:
$ python -m py_compile test.py
这导致字节码生成并放置在 test.pyc
文件中。内容可以再次反汇编:
>>> import dis
>>> dis.dis(open('test.pyc').read())
这是输出:
>> 0 ROT_THREE
1 <243> 2573
>> 4 <157> 19800
>> 7 BUILD_CLASS
8 DUP_TOPX 0
11 STOP_CODE
12 STOP_CODE
>> 13 STOP_CODE
14 STOP_CODE
15 STOP_CODE
16 STOP_CODE
17 POP_TOP
18 STOP_CODE
19 STOP_CODE
20 STOP_CODE
21 BINARY_AND
22 STOP_CODE
23 STOP_CODE
24 STOP_CODE
25 POP_JUMP_IF_TRUE 13
28 STOP_CODE
29 STOP_CODE
30 LOAD_CONST 0 (0)
33 MAKE_FUNCTION 0
36 STORE_NAME 0 (0)
39 LOAD_CONST 1 (1)
42 RETURN_VALUE
43 STORE_SLICE+0
44 ROT_TWO
45 STOP_CODE
46 STOP_CODE
47 STOP_CODE
48 DUP_TOPX 0
51 STOP_CODE
52 STOP_CODE
53 STOP_CODE
54 STOP_CODE
55 STOP_CODE
56 STOP_CODE
57 POP_TOP
58 STOP_CODE
59 STOP_CODE
60 STOP_CODE
61 INPLACE_POWER
62 STOP_CODE
63 STOP_CODE
64 STOP_CODE
65 POP_JUMP_IF_TRUE 4
68 STOP_CODE
69 STOP_CODE
70 LOAD_CONST 0 (0)
73 RETURN_VALUE
74 STORE_SLICE+0
75 POP_TOP
76 STOP_CODE
77 STOP_CODE
78 STOP_CODE
79 INPLACE_XOR
80 STORE_SLICE+0
81 STOP_CODE
82 STOP_CODE
83 STOP_CODE
84 STOP_CODE
85 STORE_SLICE+0
86 STOP_CODE
87 STOP_CODE
88 STOP_CODE
89 STOP_CODE
90 STORE_SLICE+0
91 STOP_CODE
92 STOP_CODE
93 STOP_CODE
94 STOP_CODE
95 STORE_SLICE+0
96 STOP_CODE
97 STOP_CODE
98 STOP_CODE
99 STOP_CODE
100 POP_JUMP_IF_TRUE 7
103 STOP_CODE
104 STOP_CODE
105 LOAD_GLOBAL 29541 (29541)
108 LOAD_GLOBAL 28718 (28718)
111 SETUP_EXCEPT 884 (to 998)
114 STOP_CODE
115 STOP_CODE
116 STOP_CODE
117 BUILD_TUPLE 28527
120 POP_TOP
121 STOP_CODE
122 STOP_CODE
123 STOP_CODE
124 POP_JUMP_IF_TRUE 2
127 STOP_CODE
128 STOP_CODE
129 STOP_CODE
130 POP_TOP
131 INPLACE_XOR
132 STORE_SLICE+0
133 POP_TOP
134 STOP_CODE
135 STOP_CODE
136 STOP_CODE
137 LOAD_LOCALS
138 STOP_CODE
139 STOP_CODE
140 STOP_CODE
141 STOP_CODE
142 STORE_SLICE+0
143 STOP_CODE
144 STOP_CODE
145 STOP_CODE
146 STOP_CODE
147 STORE_SLICE+0
148 STOP_CODE
149 STOP_CODE
150 STOP_CODE
151 STOP_CODE
152 STORE_SLICE+0
153 STOP_CODE
154 STOP_CODE
155 STOP_CODE
156 STOP_CODE
157 POP_JUMP_IF_TRUE 7
160 STOP_CODE
161 STOP_CODE
162 LOAD_GLOBAL 29541 (29541)
165 LOAD_GLOBAL 28718 (28718)
168 SETUP_EXCEPT 2164 (to 2335)
171 STOP_CODE
172 STOP_CODE
173 STOP_CODE
174 STORE_SUBSCR
175 IMPORT_FROM 25711 (25711)
178 <117> 25964
181 BINARY_LSHIFT
182 POP_TOP
183 STOP_CODE
184 STOP_CODE
185 STOP_CODE
186 POP_JUMP_IF_TRUE 0
189 STOP_CODE
190 STOP_CODE
差异是惊人的。为什么字节码会因编译方式的不同而出现如此鲜明的对比?
.pyc
文件的内容不是原始的 Python 字节码指令。一个 .pyc
文件 contains
- 一个 4 字节的幻数,
- 一个 4 字节的修改时间戳,并且
- 一个编组代码对象.
你基本上就是第二次拆垃圾
如果要从.pyc
反汇编代码,可以跳过8个字节,解组代码对象,然后在代码对象上调用dis.dis
:
import dis
import marshal
with open('test.pyc', 'b') as f:
f.seek(8)
dis.dis(marshal.load(f))
请注意,.pyc
格式可以随版本自由更改,因此这可能并不总是有效。事实上,自参考文章发表以来,它已经发生了变化;他们在 Python 3.3 中的源文件大小的时间戳后添加了 4 个字节,因此在 3.3 及更高版本中,您必须跳过 12 个字节。
当天早些时候,我对文档字符串和 dis
模块进行了大量试验,遇到了一些我似乎无法找到答案的问题。
首先,我创建一个包含以下内容的文件 test.py
:
def foo():
pass
只有这个,没有别的。
然后我打开一个解释器来观察程序的字节码。你可以这样得到它:
code = compile(open('test.py').read(), '', 'exec')
第一个参数是字符串形式的代码,第二个参数用于调试目的(留空是O.K。)而第三个是模式。 single
和 exec
我都试过了。结果是一样的。
之后,你可以用dis
反编译字节码。
>>> import dis
>>> dis.dis(code)
字节码输出是这样的:
1 0 LOAD_CONST 0 (<code object foo at 0x10a25e8b0, file "", line 1>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
9 LOAD_CONST 1 (None)
12 RETURN_VALUE
有道理,这么简单的脚本。这也是有道理的。
然后我尝试像这样通过命令行编译它:
$ python -m py_compile test.py
这导致字节码生成并放置在 test.pyc
文件中。内容可以再次反汇编:
>>> import dis
>>> dis.dis(open('test.pyc').read())
这是输出:
>> 0 ROT_THREE
1 <243> 2573
>> 4 <157> 19800
>> 7 BUILD_CLASS
8 DUP_TOPX 0
11 STOP_CODE
12 STOP_CODE
>> 13 STOP_CODE
14 STOP_CODE
15 STOP_CODE
16 STOP_CODE
17 POP_TOP
18 STOP_CODE
19 STOP_CODE
20 STOP_CODE
21 BINARY_AND
22 STOP_CODE
23 STOP_CODE
24 STOP_CODE
25 POP_JUMP_IF_TRUE 13
28 STOP_CODE
29 STOP_CODE
30 LOAD_CONST 0 (0)
33 MAKE_FUNCTION 0
36 STORE_NAME 0 (0)
39 LOAD_CONST 1 (1)
42 RETURN_VALUE
43 STORE_SLICE+0
44 ROT_TWO
45 STOP_CODE
46 STOP_CODE
47 STOP_CODE
48 DUP_TOPX 0
51 STOP_CODE
52 STOP_CODE
53 STOP_CODE
54 STOP_CODE
55 STOP_CODE
56 STOP_CODE
57 POP_TOP
58 STOP_CODE
59 STOP_CODE
60 STOP_CODE
61 INPLACE_POWER
62 STOP_CODE
63 STOP_CODE
64 STOP_CODE
65 POP_JUMP_IF_TRUE 4
68 STOP_CODE
69 STOP_CODE
70 LOAD_CONST 0 (0)
73 RETURN_VALUE
74 STORE_SLICE+0
75 POP_TOP
76 STOP_CODE
77 STOP_CODE
78 STOP_CODE
79 INPLACE_XOR
80 STORE_SLICE+0
81 STOP_CODE
82 STOP_CODE
83 STOP_CODE
84 STOP_CODE
85 STORE_SLICE+0
86 STOP_CODE
87 STOP_CODE
88 STOP_CODE
89 STOP_CODE
90 STORE_SLICE+0
91 STOP_CODE
92 STOP_CODE
93 STOP_CODE
94 STOP_CODE
95 STORE_SLICE+0
96 STOP_CODE
97 STOP_CODE
98 STOP_CODE
99 STOP_CODE
100 POP_JUMP_IF_TRUE 7
103 STOP_CODE
104 STOP_CODE
105 LOAD_GLOBAL 29541 (29541)
108 LOAD_GLOBAL 28718 (28718)
111 SETUP_EXCEPT 884 (to 998)
114 STOP_CODE
115 STOP_CODE
116 STOP_CODE
117 BUILD_TUPLE 28527
120 POP_TOP
121 STOP_CODE
122 STOP_CODE
123 STOP_CODE
124 POP_JUMP_IF_TRUE 2
127 STOP_CODE
128 STOP_CODE
129 STOP_CODE
130 POP_TOP
131 INPLACE_XOR
132 STORE_SLICE+0
133 POP_TOP
134 STOP_CODE
135 STOP_CODE
136 STOP_CODE
137 LOAD_LOCALS
138 STOP_CODE
139 STOP_CODE
140 STOP_CODE
141 STOP_CODE
142 STORE_SLICE+0
143 STOP_CODE
144 STOP_CODE
145 STOP_CODE
146 STOP_CODE
147 STORE_SLICE+0
148 STOP_CODE
149 STOP_CODE
150 STOP_CODE
151 STOP_CODE
152 STORE_SLICE+0
153 STOP_CODE
154 STOP_CODE
155 STOP_CODE
156 STOP_CODE
157 POP_JUMP_IF_TRUE 7
160 STOP_CODE
161 STOP_CODE
162 LOAD_GLOBAL 29541 (29541)
165 LOAD_GLOBAL 28718 (28718)
168 SETUP_EXCEPT 2164 (to 2335)
171 STOP_CODE
172 STOP_CODE
173 STOP_CODE
174 STORE_SUBSCR
175 IMPORT_FROM 25711 (25711)
178 <117> 25964
181 BINARY_LSHIFT
182 POP_TOP
183 STOP_CODE
184 STOP_CODE
185 STOP_CODE
186 POP_JUMP_IF_TRUE 0
189 STOP_CODE
190 STOP_CODE
差异是惊人的。为什么字节码会因编译方式的不同而出现如此鲜明的对比?
.pyc
文件的内容不是原始的 Python 字节码指令。一个 .pyc
文件 contains
- 一个 4 字节的幻数,
- 一个 4 字节的修改时间戳,并且
- 一个编组代码对象.
你基本上就是第二次拆垃圾
如果要从.pyc
反汇编代码,可以跳过8个字节,解组代码对象,然后在代码对象上调用dis.dis
:
import dis
import marshal
with open('test.pyc', 'b') as f:
f.seek(8)
dis.dis(marshal.load(f))
请注意,.pyc
格式可以随版本自由更改,因此这可能并不总是有效。事实上,自参考文章发表以来,它已经发生了变化;他们在 Python 3.3 中的源文件大小的时间戳后添加了 4 个字节,因此在 3.3 及更高版本中,您必须跳过 12 个字节。