Czy istnieje standardowy sposób wyświetlania nazw modułów Pythona w pakiecie?

101

Czy istnieje prosty sposób na wyświetlenie nazw wszystkich modułów w pakiecie bez używania __all__?

Na przykład, biorąc pod uwagę ten pakiet:

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

Zastanawiam się, czy istnieje standardowy lub wbudowany sposób zrobienia czegoś takiego:

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

Podejście ręczne polegałoby na iteracji ścieżek wyszukiwania modułów w celu znalezienia katalogu pakietu. Następnie można by wyświetlić listę wszystkich plików w tym katalogu, odfiltrować pliki py / pyc / pyo o unikatowych nazwach, usunąć rozszerzenia i zwrócić tę listę. Ale wydaje się, że to sporo pracy w związku z czymś, co mechanizm importu modułów już wykonuje wewnętrznie. Czy ta funkcjonalność jest gdzieś ujawniona?

DNS
źródło

Odpowiedzi:

23

Może to zrobi to, czego szukasz?

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
źródło
1
Dodałbym 'i module! = " Init .py"' ​​do końcowego 'if', ponieważ init .py nie jest tak naprawdę częścią pakietu. A .pyo to kolejne prawidłowe rozszerzenie. Poza tym użycie imp.find_module to naprawdę dobry pomysł; Myślę, że to właściwa odpowiedź.
DNS
3
Nie zgadzam się - możesz zaimportować init bezpośrednio, więc po co to specjalnie? Z pewnością nie jest na tyle wyjątkowy, by łamać zasady. ;-)
cdleary
6
Prawdopodobnie powinieneś używać imp.get_suffixes()zamiast odręcznej listy.
itsadok
3
Zwróć też uwagę, że to nie działa w przypadku podpakietów, takich jakxml.sax
itsadok
1
To naprawdę zły sposób. Nie można wiarygodnie stwierdzić, co to jest moduł, z rozszerzenia pliku.
wim
189

Używając python2.3 i nowszych wersji , możesz również użyć pkgutilmodułu:

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

EDYCJA: Zauważ, że parametr nie jest listą modułów, ale listą ścieżek, więc możesz zrobić coś takiego:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
źródło
15
Jest to niepokojąco nieudokumentowane, ale wydaje się najlepszym sposobem na zrobienie tego. Mam nadzieję, że nie masz nic przeciwko, że dodałem notatkę.
itsadok
13
pkgutiljest w wersji python2.3 i nowszych . Ponadto, chociaż pkgutil.iter_modules()nie będzie działać rekurencyjnie, istnieje pkgutil.walk_packages()również, który będzie powtarzał się. Jednak dzięki za wskazanie tego pakietu.
Sandip Bhattacharya
Dlaczego iter_modulesnie działa w przypadku importu absolutnego a.b.testpkg? Daje mi[]
Hussain
Przeoczyłem twoją EDYCJĘ :(. Przepraszam. Działa po przejrzeniu drugiego fragmentu.
Hussain
1
Nie mogę potwierdzić, że pkgutil.walk_packages()powtarza się, daje mi to samo wyjście, co pkgutil.iter_modules(), więc myślę, że odpowiedź jest niekompletna.
pierwszy
29
import module
help(module)
Tryptyk
źródło
2
Chociaż help wymienia zawartość pakietu na dole tekstu pomocy, pytanie jest bardziej podobne do tego, jak to zrobić: f (nazwa_pakietu) => ["nazwa_modułu", "nazwa_modułu2"]. Przypuszczam, że mógłbym przeanalizować ciąg zwrócony przez pomoc, ale wydaje się to bardziej okrężne niż wyświetlenie katalogu.
DNS
1
@DNS: help()drukuje rzeczy, nie zwraca ciągu.
Junuxx
Zgadzam się, że jest to okrężna droga, ale wysłała mnie do króliczej nory, aby zobaczyć, jak help()działa. W każdym razie, wbudowany pydocmoduł może pomóc wypluć Łańcuch help()Dzielenie na strony: import pydoc; pydoc.render_doc('mypackage').
soja
8

Nie wiem, czy coś przeoczyłem, czy też odpowiedzi są nieaktualne, ale;

Jak stwierdził użytkownik 815423426, działa to tylko w przypadku obiektów na żywo, a wymienione moduły to tylko moduły, które zostały wcześniej zaimportowane.

Listowanie modułów w pakiecie wydaje się naprawdę łatwe przy użyciu inspect :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
źródło
Wstawiłem importowane = Wstawiłem import import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule), ale ścieżka importd to ~ / myproj / __ init .py, a m to lista z (mymod, '~ /myproj/mymod/__init__.py ')
hithwen
1
@hithwen Nie zadawaj pytań w komentarzach, zwłaszcza jeśli nie są one bezpośrednio powiązane. Bycie dobrym Samarytaninem: używaj imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importuje moduł najwyższego poziomu, zobacz dokumentację .
siebz0r
Hmm, to obiecujące, ale nie działa na mnie. Kiedy zrobić import inspect, mypackagei wtedy inspect.getmembers(my_package, inspect.ismodule)dostaję pustą listę, choć na pewno mają różne moduły w nim.
Amelio Vazquez-Reina
1
W rzeczywistości wydaje się to działać tylko wtedy, gdy ja, import my_package.fooa nie tylko import mypackage, w takim przypadku wraca foo. Ale to mija się z celem
Amelio Vazquez-Reina
3
@ user815423426 Masz całkowitą rację ;-) Wygląda na to, że coś przeoczyłem.
siebz0r
3

To jest wersja rekurencyjna, która działa z Pythonem 3.6 i nowszymi wersjami:

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
źródło
Jaka jest zaleta używania os.scandirjako menedżera kontekstu zamiast bezpośredniego iterowania po wpisach wyników?
monkut
1
@monkut Zobacz docs.python.org/3/library/os.html#os.scandir, które sugerują użycie go jako menedżera kontekstu, aby upewnić się, że closezostanie wywołany, gdy skończysz z nim, aby upewnić się, że wszystkie wstrzymane zasoby zostaną zwolnione.
tacaswell
to nie działa, rezamiast tego wyświetla listę wszystkich pakietów, ale dodaje je re.do wszystkich
Tushortz
1

Na podstawie przykładu Cdleary, oto rekurencyjna ścieżka do listy wersji dla wszystkich modułów podrzędnych:

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)
Vajk Hermecz
źródło
0

Powinno to zawierać listę modułów:

help("modules")
Ammon
źródło
0

Jeśli chcesz zobaczyć informacje o swoim pakiecie poza kodem Pythona (z wiersza poleceń), możesz użyć do tego 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>

Będziesz miał ten sam rezultat, co pydoc, ale wewnątrz tłumacza korzystającego z pomocy

>>> import <my package>
>>> help(<my package>)
Vlad Bezden
źródło
-2
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]

źródło
Działa to tylko w przypadku modułów, a nie pakietów. Wypróbuj to na loggingpakiecie Pythona, aby zobaczyć, o co mi chodzi. Rejestrowanie zawiera dwa moduły: handlers i config. Twój kod zwróci listę 66 elementów, która nie zawiera tych dwóch nazw.
DNS
-3

print dir (moduł)

QueueHammer
źródło
1
Zawiera listę zawartości modułu, który został już zaimportowany. Szukam sposobu na wyświetlenie zawartości pakietu, który nie został jeszcze zaimportowany, tak jak robi to „from x import *”, gdy wszystko nie jest określone.
DNS
from x import * najpierw importuje moduł, a następnie kopiuje wszystko do bieżącego modułu.
Seb
Zdałem sobie sprawę, że „from x import *” w rzeczywistości nie importuje podmodułów pakietu, z powodu problemów z rozróżnianiem wielkości liter w systemie Windows. Podałem to tylko jako przykład tego, co chciałem zrobić; Zredagowałem to, aby uniknąć nieporozumień.
DNS
Zawiera listę wszystkich atrybutów już zaimportowanego obiektu, a nie tylko listę podmodułów. Więc to nie odpowiada na pytanie.
bignose