-
-
[原创]Python源码解析-PYC文件
-
发表于: 2023-3-15 20:54 5922
-
PyCodeObject结构
网上有很多关于Pyc文件的介绍。Pyc实际上就是PyCodeObject结构的文件版。不同的的Python版本,其PyCodeObject结构可能会有所不同。就以当前版Python3.11,来说,其结构和Python3.9就有所差别。
不同版本的PyCodeObject结构差别无需过多讨论。该结构的定义在Include\cpython\code.h文件中定义。并且相关注释标注清楚完善,无需过多说明。
2.Pyc加载过程
直接运行Pyc
Python在命令行运行文件的时候,会进入到pymain_run_file函数中。该函数主要内容是调用pymain_run_file_obj函数。
pymain_run_file_obj函数主要调用_PyRun_AnyFileObject函数。
_PyRun_AnyFileObject主要调用_PyRun_SimpleFileObject函数。
_PyRun_SimpleFileObject函数会通过maybe_pyc_file函数来判断该文件是否是Pyc文件。
判断成功后会去通过run_pyc_file函数来执行该Pyc文件。
之后就是判断文件的头部,magic是否符合版本要求。
通过PyMarshal_ReadLastObjectFromFile函数读取Pyc文件,生成PyCodeObject对象。
随后run_eval_code_obj执行此PyCodeObject对象。
这就是直接运行Pyc时,Pyc文件的加载过程。
通过导入Pyc文件方式执行
Pyc无法直接import导入,可以通过importlib模块进行导入。
from importlib.machinery import SourcelessFileLoader
hello = SourcelessFileLoader("hello",
"pycache\\hello.cpython-311.pyc").load_module()
类SourcelessFileLoader继承于FileLoader和_LoaderBasics。通过其成员函数load_module读取文件生成moudle对象。
之后通过marshal_loads将moudle对象加载。
3.认识Python的Opcode
Python的Opcode在Include\opcode.h。
关于Opcode的含义,可在https://docs.python.org/3.11/library/dis.html中找到。
Opcode的处理在Include\
ceval.c中的_PyEval_EvalFrameDefault。可以看到switch分支处理各个Opcode。
4.基于PyCodeObject编程
动态修改Python代码。
首先准备一个函数。
通过dir函数,我们可以看到函数fun的所有属性。其中fun.code就是函数fun的PyCodeObject对象。函数fun的代码就记录在此。首先要注意的PyCodeObject所处的内存空间是只读属性,所以无法直接对PyCodeObject的成员属性进行修改。
留意可以发现,其有一个replace的函数。但该函数并不能直接修改原始PyCodeObject对象。调用该函数时,会返回一个新的PyCodeObject。
在一个新的py文件中,创建一个新函数new_fun。通过replace,生成一个新的PyCodeObject对象a。将之前fun函数的数据都存入a中。
替换new_fun的code对象。执行函数new_fun,可以发现执行了之前的fun函数。注意:code类型需要转化成function类型才可执行。
基于此,可以实现动态修改函数代码的目的。注意,这类方法只适用于function对象的函数。在Py脚本执行时,PyCodeObject各个成员的数据都处在只读内存中,如果想要修改其内容,只能先修改内存属性。