如何在Python中查找所有子模块?

13

在Python中,将一个“子”模块导入到另一个模块并列出其属性相当简单,但是当您想要导入所有子模块时,就会稍微困难一些。

我正在为现有的3D应用程序构建工具库。每个工具都有自己的菜单项和子菜单。我希望工具负责创建自己的菜单,因为这些菜单中的许多内容都基于上下文和模板而更改。我希望我的基础模块能够找到所有子模块并检查它们是否有create_menu()函数,并在找到该函数时调用它。

有什么最简单的方法可以发现所有子模块吗?


这个和什么有关吗?(来自setuptools包的pkg_resources - Mechanical snail
1
请使用pkgutil.walk_packages - Dag
1
模块不是包。 - Andrey Fedorov
正如@Dag所说,可以使用pkgutil.walk_packages - 它将返回包和模块,并使用is_pkg布尔标志来区分它们。因此,您可以使用它来递归查找您的模块。但是,您最终将导入所有内容(甚至不需要的内容),这可能不是理想的。 - Anentropic
6个回答

3

我认为做这种插件的最好方式是使用 entry_points 和用于查询它们的API。


这应该是被接受的答案。通过入口点注册模块是一种更清晰、有意识和符合 Python 风格的方法,当你想在运行时发现它们时,不会像 pkgutil.walk_packages 一样导致所有模块被导入的副作用。 - Brian Cline

1

当我还是个善良的初学者,开始用Python编程时,我为我的模块化IRC机器人编写了这个:


    # Load plugins

    _plugins = []

    def ifName(name):
        try:
            return re.match('([^_.].+)\.[^.]+', a).group(1)
        except:
            return None

    def isValidPlugin(obj):
        from common.base import PluginBase
        try:
            if obj.__base__ == PluginBase:
                return True
            else:
                return False
        except:
            return False

    plugin_names = set(ifilter(lambda a: a!=None, [ifName(a) for a in os.listdir(os.path.join(os.getcwd(), 'plugins'))]))
    for plugin_name in plugin_names:
        try:
            plugin = __import__('plugins.'+plugin_name, fromlist=['plugins'])
            valid_plugins = filter(lambda a: isValidPlugin(a), [plugin.__getattribute__(a) for a in dir(plugin)])
            _plugins.extend(valid_plugins)
        except Exception, e:
            logger.exception('Error loading plugin %s', plugin_name)

    # Run plugins

    _plugins = [klass() for klass in _plugins]

这不是安全的或者“正确”的方式,但可能还是有用的。它是非常老的代码,请不要抨击我。


是的,你应该像dfa提到的那样使用imp模块。 - deno

1

上面遍历文件系统查找子模块的解决方案,只要将每个插件实现为基于文件系统的模块就可以了。

更加灵活的方法是在您的主模块中有一个显式的插件列表,并且每个插件(无论是通过文件、动态地创建还是类的实例)都可以显式地将自己添加到该列表中。也许可以通过 registerPlugin 函数实现。

记住:“显式优于隐式”是 Python 之禅的一部分。


为了使这个工作正常运行,您必须有一种方法来确保插件模块在启动应用程序时被导入,否则它们将没有机会注册自己。这是一个先有鸡还是先有蛋的问题。如果不遍历/自动导入模块,您最终将手动导入主模块中的所有模块,从而抵消了注册函数的需要,但打破了主要不需要知道特定插件的原则。 - Anentropic

0

pkgutil.walk_packages() 似乎是正确的方法。以下是我如何找到可用模块列表并导入其中一个的步骤:

$ mkdir foo
$ touch foo/__init__.py
$ touch foo/bar.py
$ python
Python 3.8.2 (default, Jul 16 2020, 14:00:26) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo
>>> from pkgutil import walk_packages
>>> list(walk_packages(foo.__path__))
[ModuleInfo(module_finder=FileFinder('/home/don/git/zero-play/zero_play/foo'), name='bar', ispkg=False)]
>>> from importlib import import_module
>>> bar = import_module('foo.bar')


-1
你可以尝试使用 glob 函数来匹配目录:
import os
import glob

modules = glob.glob(os.path.join('/some/path/to/modules', '*.py'))

然后你可以尝试导入它们:

checked_modules
for module in modules:
    try:
        __import__(module, globals(), locals()) # returns module object
    except ImportError:
        pass
    else:
        checked_modules.append(module)

-2

imp 看起来就是我需要的准确模块!谢谢。 - Soviut
11
回答很简洁,我不明白为什么会被接受。dir() 的文档假定你的对象已经有属性了。您能详细说明一下,应该如何使用dir()来查找尚未导入的模块吗? - bignose
3
创建一个名为 foo 的目录,然后在该目录下创建 init.py 和 bar.py 两个文件。接着打开 Python 解释器,并输入以下内容:import foo foo dir(foo) from foo import bar bar这段代码的作用是导入 foo 模块,输出 foo 模块所在位置以及 foo 模块中包含的变量和函数,然后从 foo 模块中导入 bar 模块并输出 bar 模块所在位置。 - Ross Patterson
很抱歉我表达得太简洁了。我不知道答案,且怀疑根本没有合适的答案。虽然这个答案是错误的:dir()函数会列出一个对象的属性,但在模块被导入之前,它们并不是一个包的属性。我花了大约两个小时试图搞清楚,但我找到的所有答案都不起作用(我正在使用GAE,因此无法访问文件系统)。 - Andrey Fedorov
这两个都不能用来实现OP想要的功能。而且这不是一个真正的答案,应该是一个类似提示的评论。 - GergelyPolonkai
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接