理解Python中的字节码
2021年3月18日 - 由Bo 0 评论 1221 阅读
当我们运行python脚本时,会在脚本文件对应的目录下产生一个文件夹__pycache__,文件夹里会有.pyc为后缀的文件,这些就是存储了python语句的字节码。接下来回顾一下字节码的作用、生成、运行、反汇编。
字节码的作用
当python文件被当做模块导入时,才会产生字节码文件。因为少了从语句解释到字节码的过程,只是做字节码的执行是会快一点的,当下次再次运行时,只要没有修改过原py文件的内容,则会直接执行pyc文件中的字节码,以提高效率。否则将再次生成新的pyc文件。
字节码记录的是一个个操作指令,而不是机器能识别的二进制。
指定生成字节码文件
我们也可以指定生成字节码文件。
比如先创建文件夹test,并创建bc.py并输入print("hello").
我们可以如下生成字节码:
import py_compile
py_compile.compile("./test/bc.py")
# 或者用compileall
import compileall
compileall.compile_file("./test/bc.py")
# 或者指定文件夹
import compileall
compileall.compile_dir("./test")
字节码文件的运行
该pyc文件是可以直接运行的:
$ python ./__pycache__/bc.cpython-38.pyc
hello
但是当需要import非标准库的py文件时,直接运行pyc文件会报错,需要修改对应的pyc文件名。
比如现在有两个文件,a.py和b.py:
# a.py
print("in file a")
# b.py
import os
import a
print(os.getcwd())
用compileall来直接生成两个文件的字节码文件。
此时运行python ./__pycache__/b.cpython-38.pyc时是会报错的,提示在import a时遇到了ModuleNotFoundError。
此时把a.cpython-38.pyc改成a.pyc即可,再次运行就不会出现找不到模块的错误了。
不过如果b.py中没有import a的语句,则不会报错。
反汇编字节码
如果需要知道具体有哪些字节码的指令,可以用dis。比如下面是在命令行里执行的,查看数字相加的字节码,能看到加载值两次,执行相加,并返回值:
>>> def add_num(a, b):
... return a+b
>>> import dis
>>> dis.dis(add_num)
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE
也正因为查看机器码是如此的简单,而且从pyc重新转换回py文件可以用uncompyle来做到,因此如果发布只打包pyc文件仍然是不安全的。另外pyc往往依赖于解释器的版本,如果换一个python版本则有可能无法正常运行。