总的来说,我想了解我的项目中的代码究竟是从一个大框架中实际使用的代码。
首先,我想了解所有导入的内容(可能使用静态分析),然后如果可能,实际使用了哪些导入。
对于第一个问题我当然可以使用正则表达式,但我想找到一个更清洁的方法。 但我不知道如何使用ast / inspect / parser。
关于第二个问题,如果某些导入实际上未被使用,我应该能够自动找出,但我该怎么办呢?
编辑: 关于第二个问题也许最好的方法是一个简单的导入钩子,它只记录它导入的所有东西,然后调用默认的导入机制。
所以我尝试了类似的东西:
class MyLoader(object):
"""
Loader object
"""
def __init__(self):
self.loaded = set()
def find_module(self, module_name, package=None):
print("requesting %s" % module_name)
self.loaded.add(module_name)
return self
def load_module(self, fullname):
fp, pathname, stuff = imp.find_module(fullname)
imp.load_module(fullname, fp, pathname, stuff)
但是我试图导入“随机” 来自未来导入部门 ImportError:没有名为 future
的模块我认为这意味着我错过了什么...... 我没有找到使用imp进行一些导入内省,任何提示的简单例子吗?
答案 0 :(得分:1)
这种分析的问题是python的动态特性。实际上,使用的模块集可能依赖于运行时变量(即某些模块只能在某些运行时条件下导入和使用)。
可能不是最好的方法,但是如果你的代码有相当不错的测试覆盖率,你可以使用coverage.py输出来检查在测试执行期间加载了哪些模块。
答案 1 :(得分:1)
我很高兴地说,列出进口实际上非常简单。
我需要一个Importer协议的最小实现(由PEP 302定义),如果find_module返回None,它将回退到下一个。
这个简单的脚本实际上可以显示传入的程序完成的导入:
import sys
class ImportInspector(object):
def find_module(self, module, path):
print("importing module %s" % module)
if __name__ == '__main__':
progname = sys.argv[0]
# shift by one position
sys.argv = sys.argv[1:]
sys.meta_path.append(ImportInspector())
code = compile(open(progname, 'rb').read(), progname, 'exec')
exec(code)
鉴于此,可以在其上实现任何类型的技巧。 例如,我们可以跟踪集合中的导入,并在程序退出时将它们全部存储起来。
我认为我们甚至可以获得进口的层次结构并生成类似于gprof2dot的图表,但仅基于对进口的分析。
答案 2 :(得分:0)
不确定我是否应该打开另一个问题,我会尝试跟随此处。
所以现在我想做一些更有趣的事情,编写一个上下文管理器来完成场景背后的脏工作来检测我的代码进行分析。
下面的代码给了我一个非常奇怪的错误,我无法理解:
File "study_imports.py", line 59, in <module>
exec(code)
File "study_imports.py", line 55, in <module>
cl = CollectImports()
File "study_imports.py", line 15, in __init__
self.loaded = set()
RuntimeError: maximum recursion depth exceeded while calling a Python object
import os
import sys
class CollectImports(object):
"""
Import hook, adds each import request to the loaded set and dumps
them to file
"""
def __init__(self):
self.loaded = set()
def __str__(self):
return str(self.loaded)
def dump_to_file(self, fname):
"""Dump the loaded set to file
"""
dumped_str = '\n'.join(x for x in self.loaded)
open(fname, 'w').write(dumped_str)
def find_module(self, module_name, package=None):
self.loaded.add(module_name)
class CollectorContext(object):
"""Example of context manager to collect and dump on exit
XXX: not working at the moment
"""
def __init__(self, collector, argv, output_file):
self.collector = collector
self.argv = argv
self.output_file = output_file
def __enter__(self):
self.argv = self.argv[1:]
sys.meta_path.append(self.collector)
def __exit__(self, type, value, traceback):
# TODO: should assert that the variables are None, otherwise
# we are quitting with some exceptions
self.collector.dump_to_file(self.output_file)
sys.meta_path.remove(self.collector)
if __name__ == '__main__':
# main()
progname = sys.argv[0]
cl = CollectImports()
with CollectorContext(cl, sys.argv, 'imports.log'):
code = compile(open(progname).read(), progname, 'exec')
exec(code)