①.准备测试文件
②.运行测试文件
③.查看代码字节码
④.[.pyc]文件的组成部分
⑤.为什么主文件main.py未生成pyc文件
# add_test.py
def add(a, b):
c = a + b
print(c)
return c
# sub_test.py
def sub(a, b):
c = a - b
print(c)
return c
# main.py
from add_test import add
from sub_test import sub
def func():
print("Hello, world!")
if __name__ == "__main__":
func()
add(1, 2)
sub(2, 1)
$ python main.py
> Hello, world!
> 3
> 1
$ ls
> __pycache__/ add_test.py main.py sub_test.py
$ cd __pycache__/
$ ls
add_test.cpython-38.pyc sub_test.cpython-38.pyc
$ vim add_test.cpython-38.pyc
U^M
^@^@^@^@<k`_7^@^@^@ã^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^B^@^@^@@^@^@^@s^L^@^@^@d^@d^A<84>^@Z^@d^BS^@)^Cc^B^@^@^@^@^@^@^@^@^@^@^@^C^@^@^@^B^@^@^@C^@^@^@s^T^@^@^@|^@|^A^W^@}^Bt^@|^B<83>^A^A^@|^BS^@)^AN)^AÚ^Eprint)^CÚ^AaÚ^AbÚ^Ac©^@r^E^@^@^@ú3C:\Users\Administrator\Desktop\pyc_test\add_test.pyÚ^Cadd^A^@^@^@s^F^@^@^@^@^A^H^A^H^Ar^G^@^@^@N)^Ar^G^@^@^@r^E^@^@^@r^E^@^@^@r^E^@^@^@r^F^@^@^@Ú^H<module>^A^@^@^@ó^@^@^@^@
当执行python main.py后,python解释器(Cpython)会将main.py编译成一个字节码对象PyCodeObject,在PYTHON中一切都是对象,PYTHON解释器编译出来的字节码也是对象,而我们看见的.pyc文件其实就是PyChodeObject这个字节对象在硬盘上的表现形式。
当运行PYTHON程序(.py文件)时,我们会发现生成了一种.pyc文件,.pyc文件中存储着PYTHON程序编译后的字节码(字节码并不是机器码,而是可由解释器执行的低级指令集合)。
PYTHON运行机制:首先将.py文件编译成字节码,存储在.pyc文件中(该字节码在虚拟机上运行非cpu)。当PYTHON程序第二次运行时,首先程序会在硬盘中寻找.pyc文件并且修改时间相同,如果找到直接运行,否则重复上述过程。由于引入了字节码,其加载速度比之前的.py文件有所提高,而且还可以实现源码隐藏,一定程度上可以反编译。
我们可以通过dis这个python标准库来获得代码对应的字节码,这些字节码就是PyCodeObject对象中的内容(也是pyc文件中的内容)。之所以要通过dis库是因为pyc文件中存储的是二进制字节数据,无法直接阅读
In [1]: import dis
In [2]: dis.dis(lambda x, y, z: (x+y)*z)
1 0 LOAD_FAST 0 (x) # x进栈
2 LOAD_FAST 1 (y) # y进栈
4 BINARY_ADD # 栈上的数相加,也就是x与y相加
6 LOAD_FAST 2 (z) # z进栈
8 BINARY_MULTIPLY # 栈上的数相乘,x与y相加的结果与z相乘
10 RETURN_VALUE # 返回值,整个操作就是 (x+y)*z
In [6]: main = open("main.py").read()
In [7]: com = compile(main, "main.py", "exec")
In [8]: import dis
In [9]: dis.dis(com)
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('add',))
4 IMPORT_NAME 0 (add_test)
6 IMPORT_FROM 1 (add)
8 STORE_NAME 1 (add)
10 POP_TOP
2 12 LOAD_CONST 0 (0)
14 LOAD_CONST 2 (('sub',))
16 IMPORT_NAME 2 (sub_test)
18 IMPORT_FROM 3 (sub)
20 STORE_NAME 3 (sub)
22 POP_TOP
5 24 LOAD_CONST 3 (<code object func at 0x000001B176417
A80, file "main.py", line 5>)
26 LOAD_CONST 4 ('func')
28 MAKE_FUNCTION 0
30 STORE_NAME 4 (func)
9 32 LOAD_NAME 5 (__name__)
34 LOAD_CONST 5 ('__main__')
36 COMPARE_OP 2 (==)
38 POP_JUMP_IF_FALSE 66
10 40 LOAD_NAME 4 (func)
42 CALL_FUNCTION 0
44 POP_TOP
11 46 LOAD_NAME 1 (add)
48 LOAD_CONST 6 (1)
50 LOAD_CONST 7 (2)
52 CALL_FUNCTION 2
54 POP_TOP
12 56 LOAD_NAME 3 (sub)
58 LOAD_CONST 7 (2)
60 LOAD_CONST 6 (1)
62 CALL_FUNCTION 2
64 POP_TOP
>> 66 LOAD_CONST 8 (None)
68 RETURN_VALUE
Disassembly of <code object func at 0x000001B176417A80, file "main.py", line 5>:
6 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello, world!')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
上面编译出来的字节码其实也就是12个区块,每个区块都对应着一行代码,main.py总共12行代码。当main.py被PYHON编译器编译后,Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令,在当前的上下文环境中执行这条字节码,PYTHON程序就这样跑起来了。
在一个pyc文件中,包含了三部分内容,分别是:
--->1.python的magic number(类似于python版本号)
--->2.pyc文件创建时间信息
--->3.PyCodeObject对象
1.magic number是PYTHON定义的一个整数值,一般说,不同版本的python都会定义不同的magic number,目的是保证PYTHON的兼容性。因为不同版本的python生成的字节码可能不同,所以每次python运行时,解释器都要检查magic number是否与当前运行版本python的magic number不同,如果不做检查,python虚拟机执行时就可能会报错
2.pyc文件创建时间信息主要的作用就是判断pyc文件是否需要重新生成,在运行python代码时,如果python发现已经存在相应的pyc,就会去检查对应python文件的修改时间,如果pyc文件的创建时间与python文件的修改时间不同,说明python文件发生了变动,此时就会重新生成pyc文件
在main.py中我们使用了内置属性__name__,python程序在运行时,会自动为每个module都设置__name__属性,通常的作用就是这个module的名字,也就是文件名,唯一的例外就是主module,主module会的__name__会被设置为__main__。
利用这个特性,我们可以通过对不同的模块进行测试,当这个模块以主模块运行时,if __name__ == '__main__'下的代码就会执行,而当我们import该模块时,if __name__ == '__main__'下的代码不会执行
python只会对那些以后可能会被继续使用和载入的module生成pyc文件,python认为import指令对应的module属于这类,所以myfun.py生成了相应的pyc文件,而那些临时只使用一次的模块不会生成pyc,python将主模块当成这种类型的文件,这也就是main.py不会生成pyc文件的原因
浙公网安备 33010602011771号