Чи існує стандартний спосіб перерахування імен модулів Python в пакеті?


100

Чи існує простий спосіб перерахування назв усіх модулів у пакунку без використання __all__?

Наприклад, враховуючи цей пакет:

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

Мені цікаво, чи існує стандартний або вбудований спосіб зробити щось подібне:

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

Ручний підхід полягав би у перегляді шляхів пошуку модуля, щоб знайти каталог пакунка. Потім можна було перерахувати всі файли в цьому каталозі, відфільтрувати унікальні файли py / pyc / pyo, видалити розширення та повернути цей список. Але це, здається, неабияка робота для чогось, що механізм імпорту модулів вже виконує внутрішньо. Чи вистачає ця функціональність десь?

Відповіді:


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
Я додав би 'і module! = " Init .py"' ​​до остаточного 'if', оскільки init .py насправді не є частиною пакету. І .pyo - ще одне дійсне розширення. Окрім цього, використання imp.find_module - це дійсно гарна ідея; Я думаю, це правильна відповідь.
DNS

3
Я не згоден - ви можете імпортувати init безпосередньо, то чому це особливий випадок? Це, звичайно, недостатньо спеціально, щоб порушити правила. ;-)
cdleary

6
Ймовірно, вам слід скористатися imp.get_suffixes()замість вашого рукописного списку.
ісадок

3
Крім того, зауважте, що це не працює на таких підпакетах, якxml.sax
itsadok

1
Це справді поганий спосіб. Ви не можете достовірно визначити, що таке модуль із розширення імені файлу.
wim

188

Використовуючи python2.3 і вище , ви також можете використовувати pkgutilмодуль:

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

EDIT: Зверніть увагу, що параметр - це не список модулів, а список шляхів, тож ви можете зробити щось подібне:

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

15
Це не викликає занепокоєння документів, але, здається, це найбільш правильний спосіб зробити це. Сподіваюся, ви не проти, я додав нотатку.
іцадок

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 (ім'я_пакета) => ["ім'я_модуля1", "ім'я_модуля2"]. Припускаю, я міг би проаналізувати рядок, повернутий за допомогою, але це здається більш об’ємним, ніж перелік каталогу.
DNS

1
@DNS: help()друкує матеріали, вони не повертають рядок.
Junuxx

Я згоден, що це круговий шлях, але він послав мене в кролячу яму, щоб подивитися, як це help()працює. У всякому разі, вбудований pydocмодуль може допомогти виплюнути рядок , що help()Розбивати: import pydoc; pydoc.render_doc('mypackage').
sraboy

8

Не знаю, чи я щось не помічаю, чи відповіді просто застарілі, але;

Як зазначив користувач815423426, це працює лише для реальних об'єктів, а перелічені модулі - це лише модулі, які були імпортовані раніше.

Перерахування модулів у пакеті здається дуже простим за допомогою inspect :

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

Я поставив 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

У чому перевага використання os.scandirв якості диспетчера контексту замість ітерації над записами результатів безпосередньо?
монкут

1
@monkut Див. docs.python.org/3/library/os.html#os.scandir, де пропонується використовувати його як диспетчер контексту, щоб забезпечити closeвиклик, коли ви закінчите з ним, щоб звільнити будь-які утримувані ресурси.
tacaswell

це не працює, reнатомість він перелічує кожен пакет, але додає re.до всіх них
Тушорц,

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, але всередині інтерпретатора за допомогою довідки

>>> 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, щоб зрозуміти, що я маю на увазі. Протоколювання містить два модулі: обробники та конфігурацію. Ваш код поверне список із 66 елементів, який не містить цих двох назв.
DNS

-3

друкувати каталог (модуль)


1
У ньому перелічено вміст модуля, який вже імпортовано. Я шукаю спосіб перерахувати вміст пакета, який ще не імпортований, так само, як це робить 'from x import *', коли все не вказано.
DNS

з x import * спочатку імпортує модуль, а потім копіює все в поточний модуль.
Себ

Я зрозумів, що 'from x import *' насправді не імпортує підмодулі пакету через проблеми з чутливістю до регістру в Windows. Я включив це лише як приклад того, що я хотів зробити; Я відредагував це питання, щоб уникнути плутанини.
DNS

У ньому перелічені всі атрибути вже імпортованого об’єкта, а не лише список підмодулів. Тож це не відповідає на питання.
bignose
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.