正如您所说,因为发行版不是它们包含的模块,所以我们遇到了一个问题:发行版的典型安装过程——即 afaik,一个包的集合以及一个安装程序——是下载,解压,然后运行 setup.py,它会处理安装过程的其余部分。
结果是,即使给定一个 Python 发行版,如果不运行 setup.py,您实际上也无法知道它会做什么。可能存在约定,您可能能够提取大量信息并制定很多好的猜测,但运行该“setup.py”文件确实是查看它实际安装到站点包中的唯一方法。因此,parse_requirements,或者实际上任何 pip 内部结构对您都没有用处,除非您只对发行版感兴趣。
话虽如此,我认为解决问题的最佳方法是:
- 设置没有站点包的虚拟环境
-
pip -r requirements.txt 实际安装所有包
- 浏览
sys.path,查找.py、.pyc 和__init__.py? 文件的子文件夹以构建模块列表。
- 杀死那个 virtualenv 并继续前进。
我不确定第三步是否可以通过其他更好的方式实现。此外,您仍然冒着丢失动态创建的模块或其他技巧的风险,但这应该会捕获大部分模块。
编辑:
这里有一些代码应该适用于除 zip 文件之外的所有内容:
import sys, os
def walk_modules_os(root):
def inner_walk(dir_path, mod_path):
filelist = os.listdir(dir_path)
pyfiles = set()
dirs = []
for name in filelist:
if os.path.isdir(os.path.join(dir_path, name)):
dirs.append(name)
else:
pre, ext = os.path.splitext(name)
if ext in ('.py', '.pyc', '.pyo'):
pyfiles.add(pre)
if len(mod_path):
if '__init__' not in pyfiles:
return
pyfiles.remove('__init__')
yield mod_path
for pyfile in pyfiles:
yield mod_path + (pyfile,)
for directory in dirs:
sub = os.path.join(dir_path, directory)
for mod in inner_walk(sub, mod_path + (directory,)):
yield mod
root = os.path.realpath(root)
if not os.path.isdir(root):
return iter([])
return iter(inner_walk(root, tuple()))
# you could collect as a set of tuples and do set subtraction, too
for path in sys.path:
for mod in walk_modules_os(path):
print mod
编辑 2:
好吧,笨蛋。 GWW 有正确的想法。比我的更好的解决方案。