Gibt es eine Standardmethode, um Namen von Python-Modulen in einem Paket aufzulisten?

100

Gibt es eine einfache Möglichkeit, die Namen aller Module in einem Paket aufzulisten, ohne sie zu verwenden __all__?

Zum Beispiel angesichts dieses Pakets:

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

Ich frage mich, ob es einen Standard oder eine integrierte Methode gibt, um so etwas zu tun:

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

Der manuelle Ansatz besteht darin, die Modulsuchpfade zu durchlaufen, um das Verzeichnis des Pakets zu finden. Man könnte dann alle Dateien in diesem Verzeichnis auflisten, die eindeutig benannten py / pyc / pyo-Dateien herausfiltern, die Erweiterungen entfernen und diese Liste zurückgeben. Dies scheint jedoch eine Menge Arbeit für etwas zu sein, das der Modulimportmechanismus bereits intern erledigt. Ist diese Funktionalität irgendwo verfügbar?

DNS
quelle

Antworten:

22

Vielleicht macht das, wonach Sie suchen?

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
quelle
1
Ich würde 'und module! = " Init .py"' ​​zum endgültigen 'if' hinzufügen , da init .py nicht wirklich Teil des Pakets ist. Und .pyo ist eine weitere gültige Erweiterung. Abgesehen davon ist die Verwendung von imp.find_module eine wirklich gute Idee. Ich denke das ist die richtige Antwort.
DNS
3
Ich bin anderer Meinung - Sie können init direkt importieren. Warum also Sonderfall? Es ist sicher nicht speziell genug, um die Regeln zu brechen. ;-)
cdleary
6
Sie sollten wahrscheinlich imp.get_suffixes()anstelle Ihrer handgeschriebenen Liste verwenden.
Itsadok
3
Beachten Sie auch, dass dies bei Unterpaketen wiexml.sax
itsadok
1
Das ist ein wirklich schlechter Weg. An der Dateinamenerweiterung können Sie nicht zuverlässig erkennen, was ein Modul ist.
wim
188

Mit Python2.3 und höher können Sie auch das pkgutilModul verwenden:

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

BEARBEITEN: Beachten Sie, dass der Parameter keine Liste von Modulen, sondern eine Liste von Pfaden ist. Daher möchten Sie möglicherweise Folgendes tun:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
quelle
15
Dies ist beunruhigend undokumentiert, scheint aber der richtigste Weg zu sein, dies zu tun. Ich hoffe, es macht Ihnen nichts aus, ich habe die Notiz hinzugefügt.
Itsadok
13
pkgutilgibt es in python2.3 und höher tatsächlich . Auch während pkgutil.iter_modules()nicht rekursiv arbeiten, gibt es eine pkgutil.walk_packages()als auch, was wird Rekursion. Vielen Dank für den Hinweis auf dieses Paket.
Sandip Bhattacharya
Warum iter_modulesfunktioniert das nicht für den absoluten Import a.b.testpkg? Es gibt mir[]
Hussain
Ich habe Ihre EDIT übersehen :(. Entschuldigung. Es funktioniert, nachdem ich dem zweiten Snippet gefolgt bin.
Hussain
1
Ich kann nicht bestätigen, dass es pkgutil.walk_packages()wiederkehrt, es gibt mir die gleiche Ausgabe wie pkgutil.iter_modules(), daher denke ich, dass die Antwort unvollständig ist.
21.
29
import module
help(module)
Triptychon
quelle
2
Obwohl die Hilfe den Paketinhalt am Ende des Hilfetextes auflistet, ist die Frage eher wie folgt: f (Paketname) => ["Modul1_Name", "Modul2_Name"]. Ich nehme an, ich könnte die von der Hilfe zurückgegebene Zeichenfolge analysieren, aber das scheint mehr Umweg zu sein, als das Verzeichnis aufzulisten.
DNS
1
@DNS: help()druckt Sachen, es wird keine Zeichenfolge zurückgegeben.
Junuxx
Ich bin damit einverstanden, dass dies ein Umweg ist, aber er hat mich in ein Kaninchenloch geschickt, um zu sehen, wie es help()funktioniert. Auf jeden Fall kann das eingebaute pydocModul dabei helfen, die Zeichenfolge auszuspucken, die help()paginiert : import pydoc; pydoc.render_doc('mypackage').
Sraboy
7

Ich weiß nicht, ob ich etwas übersehen habe oder ob die Antworten nur veraltet sind, aber;

Wie von user815423426 angegeben, funktioniert dies nur für Live-Objekte und die aufgelisteten Module sind nur Module, die zuvor importiert wurden.

Das Auflisten von Modulen in einem Paket scheint mit inspect wirklich einfach zu sein :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
quelle
Ich habe importiert = importieren __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule), aber der importierte Pfad ist ~ / myproj / __ init .py und m ist eine Liste mit (mymod, '~ /myproj/mymod/__init__.py ')
zwischen dem
1
@hithwen Stellen Sie keine Fragen in den Kommentaren, insbesondere wenn sie nicht direkt miteinander verbunden sind. Ein guter Samariter sein: Verwenden imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importiert das Modul der obersten Ebene, siehe Dokumentation .
siebz0r
Hmm, das ist vielversprechend, aber es funktioniert nicht für mich. Wenn ich das tue import inspect, mypackageund dann inspect.getmembers(my_package, inspect.ismodule)bekomme ich eine leere Liste, obwohl ich sicherlich verschiedene Module darin habe.
Amelio Vazquez-Reina
1
Tatsächlich scheint dies nur zu funktionieren, wenn ich import my_package.foound nicht nur import mypackage, in welchem ​​Fall es dann zurückkehrt foo. Aber das macht den Zweck
zunichte
3
@ user815423426 Du hast absolut recht ;-) Scheint, als hätte ich etwas übersehen.
siebz0r
2

Dies ist eine rekursive Version, die mit Python 3.6 und höher funktioniert:

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
quelle
Was ist der Vorteil der Verwendung os.scandirals Kontextmanager, anstatt die Ergebniseinträge direkt zu durchlaufen?
Mönch
1
@monkut Siehe docs.python.org/3/library/os.html#os.scandir, in dem vorgeschlagen wird, es als Kontextmanager zu verwenden, um sicherzustellen, dass closees aufgerufen wird, wenn Sie damit fertig sind, um sicherzustellen, dass alle gehaltenen Ressourcen freigegeben werden.
Tacaswell
Dies funktioniert nicht, restattdessen werden alle Pakete aufgelistet, aber re.zu allen
hinzugefügt
1

Basierend auf dem Beispiel von cdleary finden Sie hier einen rekursiven Versionslistenpfad für alle Submodule:

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
quelle
0

Dies sollte die Module auflisten:

help("modules")
Ammon
quelle
0

Wenn Sie Informationen zu Ihrem Paket außerhalb des Python-Codes (über eine Eingabeaufforderung) anzeigen möchten, können Sie pydoc verwenden.

# 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>

Sie haben das gleiche Ergebnis wie pydoc, jedoch innerhalb des Dolmetschers mithilfe der Hilfe

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

quelle
Das funktioniert nur für Module, nicht für Pakete. Probieren Sie es in Pythons loggingPaket aus, um zu sehen, was ich meine. Die Protokollierung enthält zwei Module: Handler und Konfiguration. Ihr Code gibt eine Liste mit 66 Elementen zurück, die diese beiden Namen nicht enthält.
DNS
-3

Druckverzeichnis (Modul)

QueueHammer
quelle
1
Hier wird der Inhalt eines Moduls aufgelistet, das bereits importiert wurde. Ich suche nach einer Möglichkeit, den Inhalt eines Pakets aufzulisten, das noch nicht importiert wurde, genau wie 'from x import *', wenn nicht alles angegeben ist.
DNS
from x import * importiert zuerst das Modul und kopiert dann alles in das aktuelle Modul.
Seb
Ich habe festgestellt, dass 'from x import *' aufgrund von Problemen mit der Groß- und Kleinschreibung unter Windows keine Untermodule eines Pakets importiert. Ich habe das nur als Beispiel dafür aufgenommen, was ich tun wollte; Ich habe es außer Frage gestellt, um Verwirrung zu vermeiden.
DNS
Dadurch werden alle Attribute eines bereits importierten Objekts aufgelistet, nicht nur eine Liste der Untermodule. Es beantwortet also nicht die Frage.
Bignose