Есть ли стандартный способ перечислить имена модулей Python в пакете?

101

Есть ли простой способ перечислить имена всех модулей в пакете без использования __all__?

Например, с учетом этого пакета:

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

Мне интересно, есть ли стандартный или встроенный способ сделать что-то вроде этого:

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

Ручной подход заключался бы в переборе путей поиска модулей, чтобы найти каталог пакета. Затем можно было бы перечислить все файлы в этом каталоге, отфильтровать файлы py / pyc / pyo с уникальными именами, удалить расширения и вернуть этот список. Но это похоже на изрядную работу для того, что механизм импорта модулей уже выполняет внутри. Эта функция доступна где-нибудь?

DNS
источник

Ответы:

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)])
cdleary
источник
1
Я бы добавил 'and module! = " Init .py"' ​​в окончательное 'if', поскольку init .py на самом деле не является частью пакета. И .pyo - еще одно допустимое расширение. Кроме того, использование imp.find_module - действительно хорошая идея; Думаю, это правильный ответ.
DNS
3
Я не согласен - вы можете импортировать init напрямую, так почему это особый случай? Конечно, этого недостаточно, чтобы нарушать правила. ;-)
cdleary
6
Вам, вероятно, следует использовать imp.get_suffixes()вместо рукописного списка.
itsadok
3
Также обратите внимание, что это не работает с такими субпакетами, какxml.sax
itsadok
1
Это действительно плохой способ. По расширению имени файла нельзя точно сказать, что это за модуль.
wim
190

Используя 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])]
jp.
источник
15
Это досадно недокументировано, но кажется наиболее правильным способом сделать это. Надеюсь, вы не против, я добавил записку.
itsadok
13
pkgutilесть в python2.3 и выше . Кроме того, хотя pkgutil.iter_modules()не будет работать рекурсивно, есть pkgutil.walk_packages()также, который будет рекурсивно. Тем не менее, спасибо за указатель на этот пакет.
Sandip Bhattacharya
Почему iter_modulesне работает на абсолютный импорт лайк a.b.testpkg? Это дает мне[]
Хуссейн
Я пропустил ваше РЕДАКТИРОВАНИЕ :(. Извините. Он работает после того, как я следил за вторым фрагментом.
Хуссейн,
1
Я не могу подтвердить, что pkgutil.walk_packages()рекурсии, это дает мне тот же результат, что и pkgutil.iter_modules(), поэтому я думаю, что ответ неполный.
rwst
29
import module
help(module)
Триптих
источник
2
Хотя справка действительно перечисляет содержимое пакета внизу текста справки, вопрос больше связан с тем, как это сделать: 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']
siebz0r
источник
Я поставил import = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule), но путь importd - ~ / 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
На самом деле, это работает, только если я, import my_package.fooа не только import mypackage, и в этом случае он возвращается foo. Но это побеждает цель
Амелио Васкес-Рейна
3
@ user815423426 Вы абсолютно правы ;-) Похоже, я что-то упустил.
siebz0r
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
Tacaswell
источник
В чем преимущество использования os.scandirв качестве диспетчера контекста вместо прямого перебора записей результатов?
монкут
1
@monkut См. docs.python.org/3/library/os.html#os.scandir, где предлагается использовать его в качестве диспетчера контекста, чтобы гарантировать, что он closeбудет вызываться, когда вы закончите с ним, чтобы гарантировать, что все удерживаемые ресурсы будут освобождены.
tacaswell 04
это не работает для 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

Здесь должны быть перечислены модули:

help("modules")
Аммон
источник
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, но внутри интерпретатора с помощью справки

>>> 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("__")]

источник
Это работает только для модулей, но не для пакетов. Попробуйте использовать его в loggingпакете Python, чтобы понять, что я имею в виду. Ведение журнала содержит два модуля: обработчики и config. Ваш код вернет список из 66 элементов, в который не входят эти два имени.
DNS
-3

каталог печати (модуль)

QueueHammer
источник
1
Это перечисляет содержимое уже импортированного модуля. Я ищу способ перечислить содержимое пакета, который еще не был импортирован, точно так же, как 'from x import *', когда все не указано.
DNS
from x import * сначала импортирует модуль, а затем копирует все в текущий модуль.
Себ
Я понял, что «from x import *» фактически не импортирует подмодули пакета из-за проблем с чувствительностью к регистру в Windows. Я включил это только как пример того, что я хотел сделать; Я отредактировал его вне всяких сомнений, чтобы избежать путаницы.
DNS
Здесь перечислены все атрибуты уже импортированного объекта, а не только список подмодулей. Так что это не отвечает на вопрос.
bignose