列出所有属于Python包的模块?

131
有没有一种简单的方法来查找属于Python包的所有模块?我找到了这个旧的讨论,但它并不是很确定。在我基于os.listdir()推出自己的解决方案之前,我想有一个明确的答案。

6
有更通用的解决方案,Python包不一定在文件系统中的目录中,也可以在zip文件内部。 - u0b34a0f6ae
4
为什么要重复发明轮子?如果Python 4中获得超级模块,并更新了pkgutil,那么我的代码仍然可以工作。我喜欢使用现有的抽象化工具。使用提供的明显方法,它已经被测试并知道可以工作。重新实现它,现在你必须自己找到并解决每个角落的问题。 - u0b34a0f6ae
1
@S.Lott:所以每次应用程序启动时,它都会解压缩自己的egg(如果安装在其中)来进行检查吗?请提交一个补丁,以重新发明此功能:http://git.gnome.org/cgit/kupfer/tree/kupfer/plugins.py#n17。请考虑到egg和普通目录,不要超过20行。 - u0b34a0f6ae
1
@S.Lott:为什么你不明白这是相关的,这是你无法理解的。通过编程发现这一点是关于应用程序对包内容感兴趣,而不是用户。 - u0b34a0f6ae
3
当然,我的意思是编程方面的!否则我就不会提到“使用os.listdir()来自己实现解决方案”了。 - static_rtti
显示剩余9条评论
6个回答

173

是的,你需要使用pkgutil或类似方法来处理所有的包,不管它们是在蛋文件(eggs)还是压缩文件(zips)中(这些情况下,os.listdir不能帮助你)。

import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import email

package = email
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
    print "Found submodule %s (is a package: %s)" % (modname, ispkg)

如何同时导入它们?您可以像平常一样使用__import__

import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import email

package = email
prefix = package.__name__ + "."
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix):
    print "Found submodule %s (is a package: %s)" % (modname, ispkg)
    module = __import__(modname, fromlist="dummy")
    print "Imported", module

11
pkgutil.iter_modules返回的importer是什么?我能用它来导入一个模块吗,而不是使用似乎有些“笨拙”的__import__(modname, fromlist="dummy")吗? - MestreLion
33
我可以像这样使用导入器:m = importer.find_module(modname).load_module(modname),然后m就是模块,例如:m.myfunc() - chrisleague
1
@chrisleague 我之前在 Python 2.7 中使用了您的方法,但现在我需要转向 Python 3.4。您知道,在 Python 3 中,pkutil.iter_modules 返回的是 (module_finder, name, ispkg) 而不是 (module_loader, name, ispkg)。我该怎么做才能让它像以前一样工作? - crax
不是,我用了两个下划线,但stackoverflow会把其中一个视为斜体并吃掉。 - Apostolos
@Apostolos 这是答案:Python 词汇表 “从技术上讲,包是带有 __path__ 属性的 Python 模块。” - 因此我们只能从包中获取路径,而不能从任何模块中获取。 - augustomen
显示剩余3条评论

53

这项工作的正确工具是pkgutil.walk_packages。

要列出系统上的所有模块:

import pkgutil
for importer, modname, ispkg in pkgutil.walk_packages(path=None, onerror=lambda x: None):
    print(modname)

请注意,walk_packages会导入所有子包,但不会导入子模块。

如果您希望列出某个包的所有子模块,则可以使用类似以下的代码:

import pkgutil
import scipy
package=scipy
for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__,
                                                      prefix=package.__name__+'.',
                                                      onerror=lambda x: None):
    print(modname)

iter_modules仅列出一级模块。walk_packages获取所有子模块。例如,在scipy的情况下,walk_packages返回

scipy.stats.stats

虽然iter_modules只返回

scipy.stats

pkgutil文档(http://docs.python.org/library/pkgutil.html)没有列出在/usr/lib/python2.6/pkgutil.py中定义的所有有趣的函数。

也许这意味着这些函数不属于"公共"接口,因此可能会发生变化。

然而,至少在Python 2.6(以及之前的版本?),pkgutil带有一个walk_packages方法,可以递归遍历所有可用的模块。


6
现在文档中包含了 walk_packages 方法:http://docs.python.org/library/pkgutil.html#pkgutil.walk_packages。 - Mechanical snail
1
您的第二个示例代码会产生以下错误:“AttributeError: 'module' object has no attribute 'path'” - 我并没有使用 'scipy' 库进行测试,而是使用了几个其他的库。这与 Python 版本有关吗?(我使用的是 Python 2.7) - Apostolos
1
@Apostolos:在path前后应该有两个下划线(_)——也就是说,使用package.__path__而不是package._path_。尝试剪切和粘贴代码可能会更容易些。 - unutbu
当我写评论时有两个下划线! 但它们已被系统删除。我的错; 我应该放三个下划线。但是,如果我想使用斜体,这将没问题,但我不需要! 这是一个损失-损失的情况。:) 无论如何,当我运行代码时,我当然使用了两个下划线。(我复制粘贴了代码。) - Apostolos
@Apostolos:确保变量package指向一个包,而不是一个模块。模块是文件,而包是目录。所有包都有__path__属性(除非有人出于某种原因删除了该属性)。 - unutbu
我明白你的意思@unutbu...确实,我尝试使用'pip',它运行良好。谢谢! - Apostolos

3

这对我有用:

import types

for key, obj in nltk.__dict__.iteritems():
    if type(obj) is types.ModuleType: 
        print key

1
这种方法有两个问题:1. 包不总是将其子模块明确导入到顶级命名空间中;2. 包可能会将其他第三方模块导入到其顶级命名空间中。 - wim

2
感谢所有之前的回答,我刚刚将它们合并成一个函数,可以轻松地用来检索子模块。
def list_submodules(module) -> list[str]:
    """
    Args:
        module: The module to list submodules from.
    """
    # We first respect __all__ attribute if it already defined.
    submodules = getattr(module, "__all__", None)
    if submodules:
        return submodules

    # Then, we respect module object itself to get imported submodules.
    # Warning: Initially, the module object will respect the `__init__.py`
    # file, if its not exists, the object can partially load submoudles
    # by coda, so can lead `inspect` to return incomplete submodules list.
    import inspect
    submodules = [o[0] for o in inspect.getmembers(module)
                    if inspect.ismodule(o[1])]
    if submodules:
        return submodules

    # Finally we can just scan for submodules via pkgutil.
    import pkgutil
    # pkgutill will invoke `importlib.machinery.all_suffixes()`
    # to determine whether a file is a module, so if you get any
    # submoudles that are unexpected to get, you need to check
    # this function to do the confirmation.
    # If you want to retrive a directory as a submoudle, you will
    # need to clarify this by putting a `__init__.py` file in the
    # folder, even for Python3.
    return [x.name for x in pkgutil.iter_modules(module.__path__)]

然后你可以直接这样称呼它:
import module
print(list_submodules(module))

path = ...
module = importlib.import_module(path)
print(list_submodules(module))

0

我正在寻找一种重新加载我正在实时编辑的包中所有子模块的方法。 这是上面答案/评论的结合体,所以我决定将其作为答案发布在这里而不是作为评论。

package=yourPackageName
import importlib
import pkgutil
for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__, prefix=package.__name__+'.', onerror=lambda x: None):
    try:
        modulesource = importlib.import_module(modname)
        reload(modulesource)
        print("reloaded: {}".format(modname))
    except Exception as e:
        print('Could not load {} {}'.format(modname, e))

-4

以下是我突发奇想的一种方法:

>>> import os
>>> filter(lambda i: type(i) == type(os), [getattr(os, j) for j in dir(os)])
[<module 'UserDict' from '/usr/lib/python2.5/UserDict.pyc'>, <module 'copy_reg' from '/usr/lib/python2.5/copy_reg.pyc'>, <module 'errno' (built-in)>, <module 'posixpath' from '/usr/lib/python2.5/posixpath.pyc'>, <module 'sys' (built-in)>]

它肯定可以被整理和改进。

编辑:这是一个稍微好一点的版本:

>>> [m[1] for m in filter(lambda a: type(a[1]) == type(os), os.__dict__.items())]
[<module 'copy_reg' from '/usr/lib/python2.5/copy_reg.pyc'>, <module 'UserDict' from '/usr/lib/python2.5/UserDict.pyc'>, <module 'posixpath' from '/usr/lib/python2.5/posixpath.pyc'>, <module 'errno' (built-in)>, <module 'sys' (built-in)>]
>>> [m[0] for m in filter(lambda a: type(a[1]) == type(os), os.__dict__.items())]
['_copy_reg', 'UserDict', 'path', 'errno', 'sys']

注意:这也会找到可能不一定位于包的子目录中的模块,如果它们在其__init__.py文件中被引入,因此这取决于您对“包的一部分”是什么意思。


1
抱歉,这没有用。除了误报之外,它只会找到已经导入的包的子模块。 - u0b34a0f6ae

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