有没有一种标准的方法可以在软件包中列出Python模块的名称?


100

有没有一种简单的方法可以列出软件包中所有模块的名称,而无需使用__all__

例如,给定此程序包:

/testpkg
/testpkg/__init__.py
/testpkg/modulea.py
/testpkg/moduleb.py

我想知道是否有标准或内置的方式来做这样的事情:

>>> package_contents("testpkg")
['modulea', 'moduleb']

手动方法是遍历模块搜索路径,以找到包的目录。然后可以列出该目录中的所有文件,过滤出唯一命名为py / pyc / pyo的文件,剥离扩展名,然后返回该列表。但这对于模块导入机制已经在内部完成的工作来说似乎是相当多的工作。该功能在任何地方都可以使用吗?

Answers:


23

也许这会满足您的需求?

import imp
import os
MODULE_EXTENSIONS = ('.py', '.pyc', '.pyo')

def package_contents(package_name):
    file, pathname, description = imp.find_module(package_name)
    if file:
        raise ImportError('Not a package: %r', package_name)
    # Use a set because some may be both source and compiled.
    return set([os.path.splitext(module)[0]
        for module in os.listdir(pathname)
        if module.endswith(MODULE_EXTENSIONS)])

1
我会在最终的'if'中添加'and模块!=“ init .py”',因为init .py实际上不是包的一部分。.pyo是另一个有效的扩展名。除此之外,使用imp.find_module是一个很好的主意。我认为这是正确的答案。
DNS

3
我不同意-您可以直接导入init,为什么还要特殊情况呢?它肯定不够特殊,无法违反规则。;-)
cdleary

6
您可能应该使用imp.get_suffixes()而不是手写列表。
itsadok

3
另外,请注意,这不适用于子包,例如xml.sax
–itsadok,2009年

1
这是一个非常糟糕的方法。您不能从文件扩展名中可靠地分辨出什么是模块。
wim

188

使用python2.3及更高版本,您还可以使用以下pkgutil模块:

>>> import pkgutil
>>> [name for _, name, _ in pkgutil.iter_modules(['testpkg'])]
['modulea', 'moduleb']

编辑:请注意,该参数不是模块列表,而是路径列表,因此您可能需要执行以下操作:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]

15
令人不安的是,这没有记录,但似乎是最正确的方法。希望你不介意我加了纸条。
itsadok

13
pkgutil实际上python2.3及更高版本中。此外,虽然pkgutil.iter_modules()不会递归工作,有一个pkgutil.walk_packages()为好,这递归。不过感谢您指向此软件包的指针。
Sandip Bhattacharya

为什么iter_modules对于绝对导入不起作用a.b.testpkg?它给了我[]
侯赛因

我忽略了您的EDIT :(。对不起。在我按照第二个代码段进行后,它就可以使用
。– Hussain

1
我无法确认pkgutil.walk_packages()递归,它给我的输出与相同pkgutil.iter_modules(),因此我认为答案是不完整的。
rwst

29
import module
help(module)

2
尽管help确实在帮助文本的底部列出了软件包的内容,但问题更多的是如何执行此操作:f(package_name)=> [“” module1_name“,” module2_name“]。我想我可以解析help返回的字符串,但是这似乎比列出目录更为复杂。
DNS

1
@DNS:help()打印内容,不返回字符串。
Junuxx

我同意这是一种回旋的方式,但它使我跌入兔子洞以了解其help()工作原理。无论如何,内置pydoc模块可以帮助吐出help()分页的字符串:import pydoc; pydoc.render_doc('mypackage')
sraboy

8

不知道我是在忽略什么,还是答案只是过时而已;

如user815423426所述,这仅适用于活动对象,并且列出的模块仅是之前导入的模块。

使用inspect列出软件包中的模块似乎真的很容易:

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']

我已经输入了import = import __('myproj.mymod.mysubmod')m = inspect.getmembers(i,inspect.ismodule),但是导入的路径是〜/ myproj / __ init .py并且m是带有(mymod,'〜 /myproj/mymod/__init__.py')–
hithwen

1
@hithwen不要在评论中提出问题,特别是如果它们没有直接关系。做个好撒玛利亚人:使用imported = import importlib; importlib.import_module('myproj.mymod.mysubmod')__import__导入顶级模块,请参阅文档
siebz0r

嗯,这很有希望,但对我不起作用。当我这样做import inspect, mypackage,然后inspect.getmembers(my_package, inspect.ismodule)我得到一个空列表,即使我当然有它的各个模块。
阿梅里奥·瓦兹克斯·雷纳

1
实际上,这似乎仅在I上有效import my_package.foo,而不仅仅是import mypackage,在这种情况下,它将返回foo。但这没有达到目的
阿梅利奥·瓦兹奎兹·雷纳

3
@ user815423426您绝对正确;-)好像我在忽略某些东西。
siebz0r 2013年

3

这是适用于python 3.6及更高版本的递归版本:

import importlib.util
from pathlib import Path
import os
MODULE_EXTENSIONS = '.py'

def package_contents(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return set()

    pathname = Path(spec.origin).parent
    ret = set()
    with os.scandir(pathname) as entries:
        for entry in entries:
            if entry.name.startswith('__'):
                continue
            current = '.'.join((package_name, entry.name.partition('.')[0]))
            if entry.is_file():
                if entry.name.endswith(MODULE_EXTENSIONS):
                    ret.add(current)
            elif entry.is_dir():
                ret.add(current)
                ret |= package_contents(current)


    return ret

os.scandir用作上下文管理器而不是直接遍历结果条目有什么好处?
星期一

1
@monkut,请参阅docs.python.org/3/library/os.html#os.scandir,该文件建议将其用作上下文管理器,以确保close在完成处理后调用它,以确保释放任何保留的资源。
tacaswell

re对它不起作用,而是列出了每个程序包,但添加re.了所有程序包
Tushortz

1

根据cdleary的示例,这是所有子模块的递归版本列表路径:

import imp, os

def iter_submodules(package):
    file, pathname, description = imp.find_module(package)
    for dirpath, _, filenames in os.walk(pathname):
        for  filename in filenames:
            if os.path.splitext(filename)[1] == ".py":
                yield os.path.join(dirpath, filename)


0

如果您想在python代码之外查看有关软件包的信息(从命令提示符),则可以使用pydoc。

# get a full list of packages that you have installed on you machine
$ python -m pydoc modules

# get information about a specific package
$ python -m pydoc <your package>

您将获得与pydoc相同的结果,但在解释器中使用help

>>> import <my package>
>>> help(<my package>)

-2
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]

这仅适用于模块,不适用于软件包。在Python的logging包装上尝试一下,看看我的意思。日志记录包含两个模块:处理程序和配置。您的代码将返回一个包含66个项目的列表,其中不包括这两个名称。
DNS于2009年

-3

打印目录(模块)


1
那列出了已经导入的模块的内容。我正在寻找一种列出尚未导入的软件包内容的方法,就像未指定所有内容时“ from x import *”一样。
DNS

from x import *首先导入模块,然后将所有内容复制到当前模块。
勒布

我意识到,由于Windows上的区分大小写问题,“ from x import *”实际上并不导入程序包的子模块。我仅将其作为我想做的事的一个例子。为了避免混淆,我已经对其进行了编辑。
DNS

该列表列出了已导入对象的所有属性,而不仅列出了子模块。因此它无法回答问题。
bignose 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.