①.准备测试文件
②.运行测试文件
③.查看代码字节码
④.[.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]文件的组成部分

在一个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未生成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文件的原因
posted on 2022-01-27 13:48  码农青葱  阅读(150)  评论(0)    收藏  举报