是否有一种标准的方法来列出包中 Python 模块的名称?

是否有一种不使用 __all__而直接列出包中所有模块名称的方法?

例如,给定这个包:

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

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

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

手动方法是循环遍历模块搜索路径,以找到包的目录。然后可以列出该目录中的所有文件,过滤掉唯一命名的 py/pyc/pyo 文件,去掉扩展名,并返回该列表。但是对于模块导入机制已经在内部进行的工作来说,这似乎是一个相当大的工作量。这个功能是否在任何地方公开?

126914 次浏览
import module
help(module)

也许这能达到你的目的?

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)])
def package_contents(package_name):
package = __import__(package_name)
return [module_name for module_name in dir(package) if not module_name.startswith("__")]

使用 Python2.3或以上,你也可以使用 pkgutil模块:

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

编辑: 注意,pkgutil.iter_modules的参数不是一个模块列表,而是一个路径列表,所以您可能需要这样做:

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

不知道我是否忽略了什么,或者答案是否过时了,但是;

如 user815423426所述,这只适用于活动对象,列出的模块只是以前导入的模块。

使用 视察在包中列出模块似乎非常容易:

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

根据 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)

这是一个可用于 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

这里应该列出模块:

help("modules")

如果希望在 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>)

这里的其他答案将在检查包时运行包中的代码。如果您不希望这样,您可以像 这个答案那样对文件进行 grep

def _get_class_names(file_name: str) -> List[str]:
"""Get the python class name defined in a file without running code
file_name: the name of the file to search for class definitions in
return: all the classes defined in that python file, empty list if no matches"""
defined_class_names = []
# search the file for class definitions
with open(file_name, "r") as file:
for line in file:
# regular expression for class defined in the file
# searches for text that starts with "class" and ends with ( or :,
# whichever comes first
match = re.search("^class(.+?)(\(|:)", line) # noqa
if match:
# add the cleaned match to the list if there is one
defined_class_name = match.group(1).strip()
defined_class_names.append(defined_class_name)
return defined_class_names

每个包实例中都有一个 __loader__变量。因此,如果您导入包,您可以在包中找到“模块资源”:

import testpkg # change this by your package name


for mod in testpkg.__loader__.get_resource_reader().contents():
print(mod)

当然,您可以改进循环以查找“ module”名称:

import testpkg


from pathlib import Path


for mod in testpkg.__loader__.get_resource_reader().contents():
# You can filter the name like
# Path(l).suffix not in (".py", ".pyc")
print(Path(mod).stem)
    

在软件包内部,您当然可以直接使用 __loader__找到您的模块。

要完成@Metal3d 的回答,是的,您可以使用 testpkg.__loader__.get_resource_reader().contents()来列出“模块资源”,但是只有当您以“正常”的方式导入您的包并且您的加载程序是 _frozen_importlib_external.SourceFileLoader object时,它才会工作。

但是,如果您使用 zipimport导入库(例如: 在内存中加载包) ,那么您的加载程序将是一个 zipimporter object,而且它的 get_resource_reader函数不同于 import lib; 它将需要一个“ fullname”参数。

要使它在这两个加载程序中工作,只需在参数 get_resource_reader中指定您的包名称:

# An example with CrackMapExec tool


import importlib


import cme.protocols as cme_protocols




class ProtocolLoader:
def get_protocols(self):
protocols = {}
protocols_names = [x for x in cme_protocols.__loader__.get_resource_reader("cme.protocols").contents()]


for prot_name in protocols_names:
prot = importlib.import_module(f"cme.protocols.{prot_name}")
protocols[prot_name] = prot


return protocols