如何递归获取 python 包中的所有子模块?
Posted
技术标签:
【中文标题】如何递归获取 python 包中的所有子模块?【英文标题】:How do you recursively get all submodules in a python package? 【发布时间】:2018-07-30 11:11:48 【问题描述】:问题
我有一个这样的文件夹结构:
- modules
- root
- abc
hello.py
__init__.py
- xyz
hi.py
__init__.py
blah.py
__init__.py
foo.py
bar.py
__init_.py
这里是同样的字符串格式:
"modules",
"modues/__init__.py",
"modules/foo.py",
"modules/bar.py",
"modules/root",
"modules/root/__init__.py",
"modules/root/blah,py",
"modules/root/abc",
"modules/root/abc/__init__.py",
"modules/root/abc/hello.py",
"modules/root/xyz",
"modules/root/xyz/__init__.py",
"modules/root/xyz/hi.py"
我正在尝试以 python 导入样式格式打印出所有模块。 示例输出如下所示:
modules.foo
modules.bar
modules.root.blah
modules.root.abc.hello
modules.root.xyz.hi
我怎样才能在python中轻松做到这一点(如果可能没有第三方库)?
我尝试了什么
示例代码
import pkgutil
import modules
absolute_modules = []
def find_modules(module_path):
for package in pkgutil.walk_packages(module_path):
print(package)
if package.ispkg:
find_modules([package.name])
else:
absolute_modules.append(package.name)
if __name__ == "__main__":
find_modules(modules.__path__)
for module in absolute_modules:
print(module)
但是,此代码只会打印出 'foo' 和 'bar'。但不是“根”,它是子包。我也很难弄清楚如何将其转换为保留它的绝对导入风格。当前代码仅获取包/模块名称,而不是实际的绝对导入。
【问题讨论】:
为什么要问“没有任何第三方库”?您正在重新发明***(请原谅双关语),这已经由pkg_resources
(setuptools
分发的一部分)实现。
嗯,我想学习如何做到这一点,以便我可以自定义它
好的,但我仍然不明白为什么排除第三方库。
嗯,原因是 IRC 上有人建议使用收集库,该库将@decorator 引入所有想要收集的子模块。这是收集模块名称的糟糕方法。只要模块实际上在标准库中,就应该没问题。如果代码是积极维护的第三方库(在大多数情况下不是),也应该没问题。
【参考方案1】:
这使用setuptools.find_packages
(用于包)和pkgutil.iter_modules
用于它们的子模块。也支持 Python2。不需要递归,这两个函数一起使用就可以了。
import sys
from setuptools import find_packages
from pkgutil import iter_modules
def find_modules(path):
modules = set()
for pkg in find_packages(path):
modules.add(pkg)
pkgpath = path + '/' + pkg.replace('.', '/')
if sys.version_info.major == 2 or (sys.version_info.major == 3 and sys.version_info.minor < 6):
for _, name, ispkg in iter_modules([pkgpath]):
if not ispkg:
modules.add(pkg + '.' + name)
else:
for info in iter_modules([pkgpath]):
if not info.ispkg:
modules.add(pkg + '.' + info.name)
return modules
【讨论】:
我没有时间验证这个作品。所以我没有将我的答案标记为正确的版本。但请注意:len(find_abs_modules(xml)) == len(list(find_modules(xml.__path__[0])))
返回 False 并且还显示 _private
模块。
iter_modules 至少对我来说不像文档中那样起作用,并且 iter_modules 不能递归工作【参考方案2】:
所以我终于想出了如何干净利落地做到这一点,并让 pkgutil 为你处理所有的边缘情况。此代码基于 python 的 help()
函数,该函数仅显示***模块和包。
import importlib
import pkgutil
import sys
import modules
def find_abs_modules(module):
path_list = []
spec_list = []
for importer, modname, ispkg in pkgutil.walk_packages(module.__path__):
import_path = f"module.__name__.modname"
if ispkg:
spec = pkgutil._get_spec(importer, modname)
importlib._bootstrap._load(spec)
spec_list.append(spec)
else:
path_list.append(import_path)
for spec in spec_list:
del sys.modules[spec.name]
return path_list
if __name__ == "__main__":
print(sys.modules)
print(find_abs_modules(modules))
print(sys.modules)
这甚至适用于内置包。
【讨论】:
嗯......它没有在我的用例中递归扫描,但这帮助我想出了自己的解决方案:我使用pkg=importlib.import_module(import_path)
,然后递归调用find_abs_modules(pkg)
。跨度>
@TheDiveO 你能告诉我它不适合什么情况吗?我目前正在开发环境中使用它,并希望修补任何边缘情况。
在应用程序(包)foobar
内部我从包foobar.plugins
(在扫描之前导入)开始扫描,并且扫描功能没有找到foobar.plugins.footest
子包(我没有导入然而)。 footest
有一个 __init__.py
,但从未找到它,即使在将它导入到 foobar.plugins.__init__
中之后也是如此。所以我只好依靠importlib.import_module()
,因为无论如何我都需要导入找到的插件。
它在 Python 3.5.3 (Deb 9) 上
@TheDiveO 感谢您提供详细信息。我为 3.6.5 以上编写了此代码。所以它可以解释为什么它不适合你的情况(调用私有方法通常是一个坏主意)。我将很快使用相同功能的公共版本进行更新,以便每个人都可以受益。感谢您的帮助!【参考方案3】:
以下代码将为您提供代码当前工作目录中的相关包模块。
import os
import re
for root,dirname,filename in os.walk(os.getcwd()):
pth_build=""
if os.path.isfile(root+"/__init__.py"):
for i in filename:
if i <> "__init__.py" and i <> "__init__.pyc":
if i.split('.')[1] == "py":
slot = list(set(root.split('\\')) -set(os.getcwd().split('\\')))
pth_build = slot[0]
del slot[0]
for j in slot:
pth_build = pth_build+"."+j
print pth_build +"."+ i.split('.')[0]
此代码将显示:
modules.foo
modules.bar
modules.root.blah
modules.root.abc.hello
modules.root.xyz.hi
如果你在模块文件夹之外运行它。
【讨论】:
请注意,您不能依赖__init__.py
文件的存在来解决此问题,尤其是在较新版本的 Python 中,单个包可以跨越文件系统上的多个不同文件夹。例如,请参阅 Python 中 namespace packages 上的文档。以上是关于如何递归获取 python 包中的所有子模块?的主要内容,如果未能解决你的问题,请参考以下文章
如何检查 Python 包中的任何模块是不是从另一个包导入?