在动态执行、调试文件时,有时需要动态地解析源代码。如pdb,pysnooper、traceback的输出就会包括调试信息和对应的源代码行。
有不同的实现方式,不完全列举如下:
1、利用运行时帧信息,globas里的__loader__
如果利用sys.settrace()后,在回调函数中会传入frame信息。从pysnooper中提取关键代码,如下。
globs = frame.f_globals or {}
loader = globs.get('__loader__')
source = None
if hasattr(loader, 'get_source'):
try:
source = loader.get_source(module_name)
except ImportError:
pass
if source is not None:
source = source.splitlines()
frame为运行时获取。
2、利用pkgutils
通过pkgutils找到loader,利用loader的get_source方法得到源代码,以\n结尾。可以用splitlines()得到list
import pkgutils
loader=pkgutil.find_loader('backtrader.cerebro')
source=loader.get_source('backtrader.cerebro').splitlines()
3、利用Importlib
pkgutils的实现内部也用到了importlib,不过加了缓冲加快速度,所以也可以直接用importlib实现获得源码。
import importlib
loader=importlib.util.find_spec('backtrader.cerebro').loader
source=loader.get_source('backtrader.cerebro').splitlines()
4、利用sys.meta_path
importlib会使用sys.meta_path,所以也可以直接使用sys.meta_path实现。
“ Python 3 的 import 机制在查找过程中,大致顺序如下:
- 在 sys.modules 中查找,它缓存了所有已导入的模块
- 在 sys.meta_path 中查找,它支持自定义的加载器
- 在 sys.path 中查找,它记录了一些库所在的目录名
若未找到,抛出ImportError异常
”(Python 实现自动导入缺失的库:cnblogs)
所以使用sys.meta_path返回源代码,过程是用sys.meta_path返回importer或finder,调用find_spec函数得到模块信息,模块有loader属性,利用loader的get_source返回源代码。
- 如果包路径为多层,需要先用find_spec导入父包,再逐层导入子模块。
- find_spec函数签名:
def _find_spec(name, path, target=None),name为包的完整路径,path为包的父层的路径。 - sys.meta_path举例:
<class ‘_frozen_importlib.BuiltinImporter’>
<class ‘_frozen_importlib.FrozenImporter’>
<class ‘_frozen_importlib_external.PathFinder’>
举例代码:
pkg='A.B'
pkg_path=['e:\\root\\A']
for i,finder in enumerate(sys.meta_path):
print(i,finder)
modulespec=finder().find_spec('A')
modulespec=finder().find_spec(pkg,pkg_path)
if modulespec:
print(modulespec.loader.get_source('A.B')
5、对于内置包或已用pip install等安装的包。
前代码可以在包是第三方的时候,即不在系统的路径里时使用。但如果在系统的路径里有,可以直接调用sys.modules,主要的过程用以下代码表示,如查看内置的Pdb模块的源代码:
sys.modules['pdb'].__loader__.get_source('pdb')