Wie lade ich alle Module in einen Ordner?

269

Könnte mir jemand eine gute Möglichkeit bieten, ein ganzes Verzeichnis von Modulen zu importieren?
Ich habe eine Struktur wie diese:

/Foo
    bar.py
    spam.py
    eggs.py

Ich habe versucht, es einfach durch Hinzufügen __init__.pyund Ausführen in ein Paket umzuwandeln , from Foo import *aber es hat nicht so funktioniert, wie ich es mir erhofft hatte.

Evan Fosmark
quelle
2
Der init .py Weg ist ziemlich korrekt. Können Sie den Code posten, der nicht funktioniert hat?
Balpha
9
Können Sie "hat nicht funktioniert" definieren? Was ist passiert? Welche Fehlermeldung haben Sie erhalten?
S.Lott
Ist das Pythonic oder empfohlen? "Explizit ist besser als implizit."
Yangmillstheory
Explizit ist in der Tat besser. Aber möchten Sie diese nervigen "nicht gefundenen" Nachrichten wirklich jedes Mal ansprechen, wenn Sie ein neues Modul hinzufügen? Ich denke, wenn Sie ein Paketverzeichnis haben, das viele kleine Modulfunktionen enthält, dann ist dies der beste Weg, dies zu tun. Meine Annahmen sind: 1. Modul sind ziemlich einfach 2. Sie verwenden das Paket für Code-Ordnung
Ido_f

Antworten:

400

Listen Sie alle python ( .py) - Dateien im aktuellen Ordner auf und fügen Sie sie als __all__Variable ein__init__.py

from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
Anurag Uniyal
quelle
57
Grundsätzlich kann ich Python-Dateien ohne weitere Konfiguration in ein Verzeichnis ablegen und sie von einem Skript ausführen lassen, das an einer anderen Stelle ausgeführt wird.
Evan Fosmark
5
@NiallDouglas Diese Antwort ist für eine bestimmte Frage, die OP gestellt hat, er hatte keine Zip-Datei und Pyc-Dateien können einfach aufgenommen werden, und Sie vergessen auch .pyd oder .so libs usw.
Anurag Uniyal
35
Das einzige, was ich hinzufügen möchte, ist if not os.path.basename(f).startswith('_')oder zumindest if not f.endswith('__init__.py')bis zum Ende des Listenverständnisses
Pykler
10
Stellen Sie auch sicher, dass es robuster os.path.isfile(f)ist True. Das würde kaputte Symlinks und Verzeichnisse wie somedir.py/(
Eckfall
12
Fügen Sie from . import *nach der Einstellung , __all__wenn Sie Submodule zur Verfügung stehen mit wollen .(zB wie module.submodule1, module.submodule2usw.).
Ostrokach
133

Fügen Sie die __all__Variable zu folgendem hinzu __init__.py:

__all__ = ["bar", "spam", "eggs"]

Siehe auch http://docs.python.org/tutorial/modules.html

stefanw
quelle
163
Ja, ja, aber gibt es eine Möglichkeit, es dynamisch zu machen?
Evan Fosmark
27
Kombination von os.listdir()etwas Filtern, Entfernen der .pyVerlängerung und __all__.
Funktioniert hier nicht für mich als Beispielcode github.com/namgivu/python-import-all/blob/master/error_app.py . Vielleicht vermisse ich dort etwas?
Nam G VU
1
Ich habe es selbst herausgefunden - um die in diesen Modulen definierte Variable / das Objekt zu verwenden, müssen wir den vollständigen Referenzpfad verwenden, z moduleName.varName. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: Dieser Code in meiner Antwort auf eine verwandte Frage importiert alle Namen der öffentlichen Submodule in den Namespace des Pakets.
Martineau
54

Update 2017: Sie möchten wahrscheinlich importlibstattdessen verwenden.

Machen Sie das Foo-Verzeichnis zu einem Paket, indem Sie ein hinzufügen __init__.py. Fügen Sie __init__.pyhinzu:

import bar
import eggs
import spam

Da Sie möchten, dass es dynamisch ist (was eine gute Idee sein kann oder nicht), listen Sie alle py-Dateien mit dem Listenverzeichnis auf und importieren Sie sie wie folgt:

import os
for module in os.listdir(os.path.dirname(__file__)):
    if module == '__init__.py' or module[-3:] != '.py':
        continue
    __import__(module[:-3], locals(), globals())
del module

Dann machen Sie von Ihrem Code aus Folgendes:

import Foo

Sie können jetzt mit auf die Module zugreifen

Foo.bar
Foo.eggs
Foo.spam

usw. from Foo import *ist aus mehreren Gründen keine gute Idee, einschließlich Namenskonflikten und der Schwierigkeit, den Code zu analysieren.

Lennart Regebro
quelle
1
Nicht schlecht, aber vergessen Sie nicht, dass Sie auch .pyc- und .pyo-Dateien importieren können.
Evan Fosmark
7
tbh, ich finde __import__hackish, ich denke, es wäre besser, die Namen hinzuzufügen __all__und dann from . import *am Ende des Skripts zu setzen
freeforall tousez
1
Ich denke, das ist schöner als die Glob-Version.
lpapp
8
__import__ist nicht für allgemeine interpreterZwecke , sondern wird von importlib.import_module()stattdessen verwendet.
Andrew_1510
2
Das erste Beispiel war sehr hilfreich, danke! Unter Python 3.6.4 musste ich from . import eggsetc. machen, __init__.pybevor Python importieren konnte. Mit nur import eggsbekomme ich ModuleNotFoundError: No module named 'eggs'beim Versuch import Fooin das main.pyim Verzeichnis oben.
Nick
41

Ausgehend von Mihails Antwort glaube ich, dass der nicht-hackige Weg (wie in, die Dateipfade nicht direkt zu behandeln) der folgende ist:

  1. Erstellen Sie eine leere __init__.pyDatei unterFoo/
  2. Ausführen
import pkgutil
import sys


def load_all_modules_from_dir(dirname):
    for importer, package_name, _ in pkgutil.iter_modules([dirname]):
        full_package_name = '%s.%s' % (dirname, package_name)
        if full_package_name not in sys.modules:
            module = importer.find_module(package_name
                        ).load_module(full_package_name)
            print module


load_all_modules_from_dir('Foo')

Du wirst kriegen:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
quelle
Dies ist der größte Teil des Weges zu einer korrekten Antwort - es verarbeitet ZIP-Archive, schreibt jedoch weder Init noch Import. Siehe Automodinit unten.
Niall Douglas
1
Eine andere Sache: Das obige Beispiel überprüft sys.modules nicht, um festzustellen, ob das Modul bereits geladen ist. Ohne diese Überprüfung wird das Modul ein zweites Mal geladen :)
Niall Douglas
3
Wenn ich load_all_modules_from_dir ('Foo / bar') mit Ihrem Code ausführe, wird "RuntimeWarning: Übergeordnetes Modul 'Foo / bar' bei der Verarbeitung des absoluten Imports nicht gefunden" angezeigt. Um dies zu unterdrücken, muss full_package_name = '.'. Join ( dirname.split (os.path.sep) + package_name]) und importiere auch Foo.bar
Alex Dupuy
Diese RuntimeWarningNachrichten können auch vermieden werden, indem full_package_name überhaupt nicht verwendet wird : importer.find_module(package_name).load_module(package_name).
Artfunkel
Die RuntimeWarningFehler können auch (auf wohl hässliche Weise) durch Importieren des übergeordneten Elements (AKA-Verzeichnisname) vermieden werden. Ein Weg, dies zu tun, ist - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Das funktioniert natürlich nur, wenn dirnamees sich um einen relativen Einkomponentenpfad handelt. keine Schrägstriche. Persönlich bevorzuge ich den Ansatz von @ Artfunkel, stattdessen den Basispaketnamen zu verwenden.
Dannysauer
31

Python, schließen Sie alle Dateien in ein Verzeichnis ein:

Für Neulinge, die es einfach nicht zum Laufen bringen können und ihre Hände halten müssen.

  1. Erstellen Sie einen Ordner / home / el / foo und erstellen Sie eine Datei main.pyunter / home / el / foo. Geben Sie diesen Code dort ein:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Erstellen Sie ein Verzeichnis /home/el/foo/hellokitty

  3. Machen Sie eine Datei __init__.pyunter /home/el/foo/hellokittyund geben Sie diesen Code dort ein:

    __all__ = ["spam", "ham"]
  4. Erstellen Sie zwei Python-Dateien: spam.pyund ham.pyunter/home/el/foo/hellokitty

  5. Definieren Sie eine Funktion in spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Definieren Sie eine Funktion in ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Starte es:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
Eric Leschinski
quelle
1
Nicht nur für Neulinge, sondern auch für erfahrene Python-Entwickler, die klare Antworten mögen. Vielen Dank.
Michael Scheper
Die Verwendung import *wird jedoch als schlechte Python-Codierungspraxis angesehen. Wie machst du das ohne das?
Rachael Blake
16

Ich habe dieses Problem selbst satt und schrieb ein Paket namens automodinit, um es zu beheben. Sie können es von http://pypi.python.org/pypi/automodinit/ erhalten .

Die Verwendung ist wie folgt:

  1. Fügen Sie das automodinitPaket in Ihre setup.pyAbhängigkeiten ein.
  2. Ersetzen Sie alle __init__.py-Dateien wie folgt:
__all__ = ["Ich werde umgeschrieben"]
# Ändern Sie nicht die obige Zeile oder diese Zeile!
Automodinit importieren
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Alles andere, was Sie wollen, kann hier nachgehen, es wird nicht geändert.

Das ist es! Von nun an setzt das Importieren eines Moduls __all__ auf eine Liste von .py [co] -Dateien im Modul und importiert auch jede dieser Dateien so, als hätten Sie Folgendes eingegeben:

for x in __all__: import x

Daher stimmt der Effekt von "von M import *" genau mit "import M" überein.

automodinit läuft gerne in ZIP-Archiven und ist daher ZIP-sicher.

Niall

Niall Douglas
quelle
pip kann automodinit nicht herunterladen, da auf pypi nichts dafür hochgeladen wurde.
Kanzure
Danke für den Fehlerbericht auf Github. Ich habe dies in v0.13 behoben. Niall
Niall Douglas
11

Ich weiß, dass ich einen ziemlich alten Beitrag aktualisiere, und ich habe versucht, ihn zu verwenden automodinit, aber festgestellt, dass der Einrichtungsprozess für Python3 unterbrochen ist. Basierend auf Lucas Antwort habe ich eine einfachere Antwort auf dieses Problem gefunden, die mit .zip möglicherweise nicht funktioniert. Deshalb habe ich mir vorgenommen, sie hier zu teilen:

innerhalb des __init__.pyModuls von yourpackage:

#!/usr/bin/env python
import os, pkgutil
__all__ = list(module for _, module, _ in pkgutil.iter_modules([os.path.dirname(__file__)]))

und in einem anderen Paket unten yourpackage:

from yourpackage import *

Dann werden alle Module, die im Paket enthalten sind, geladen. Wenn Sie ein neues Modul schreiben, wird es ebenfalls automatisch importiert. Verwenden Sie solche Dinge natürlich mit Sorgfalt, mit großen Kräften geht eine große Verantwortung einher.

zmo
quelle
6
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
for imp, module, ispackage in pkgutil.walk_packages(path=__path__, prefix=__name__+'.'):
  __import__(module)
Ricky
quelle
5

Ich bin auch auf dieses Problem gestoßen und dies war meine Lösung:

import os

def loadImports(path):
    files = os.listdir(path)
    imps = []

    for i in range(len(files)):
        name = files[i].split('.')
        if len(name) > 1:
            if name[1] == 'py' and name[0] != '__init__':
               name = name[0]
               imps.append(name)

    file = open(path+'__init__.py','w')

    toWrite = '__all__ = '+str(imps)

    file.write(toWrite)
    file.close()

Diese Funktion erstellt eine Datei (im bereitgestellten Ordner) mit dem Namen __init__.py, die eine __all__Variable enthält , die jedes Modul im Ordner enthält.

Zum Beispiel habe ich einen Ordner mit dem Namen Test :

Foo.py
Bar.py

In das Skript, in das die Module importiert werden sollen, schreibe ich:

loadImports('Test/')
from Test import *

Dadurch wird alles importiert Testund die __init__.pyDatei Testenthält nun:

__all__ = ['Foo','Bar']
Pinguinklinge
quelle
perfekt, genau das, was ich suche
uma mahesh
4

Anurags Beispiel mit ein paar Korrekturen:

import os, glob

modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py"))
__all__ = [os.path.basename(f)[:-3] for f in modules if not f.endswith("__init__.py")]
kzar
quelle
4

Anurag Uniyal Antwort mit Verbesserungsvorschlägen!

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import os
import glob

all_list = list()
for f in glob.glob(os.path.dirname(__file__)+"/*.py"):
    if os.path.isfile(f) and not os.path.basename(f).startswith('_'):
        all_list.append(os.path.basename(f)[:-3])

__all__ = all_list  
Eduardo Lucio
quelle
3

Sehen Sie, dass Ihre __init__.pyDefinitionen __all__. Das Modul - Pakete doc sagt

Die __init__.pyDateien sind erforderlich, damit Python die Verzeichnisse als Pakete enthaltend behandelt. Auf diese Weise wird verhindert, dass Verzeichnisse mit einem allgemeinen Namen, z. B. eine Zeichenfolge, unbeabsichtigt gültige Module verbergen, die später im Modul-Suchpfad auftreten. Im einfachsten Fall __init__.pykann es sich nur um eine leere Datei handeln, es kann jedoch auch der Initialisierungscode für das Paket ausgeführt oder die __all__später beschriebene Variable festgelegt werden.

...

Die einzige Lösung besteht darin, dass der Paketautor einen expliziten Index des Pakets bereitstellt. Die import-Anweisung verwendet die folgende Konvention: Wenn der __init__.pyCode eines Pakets eine Liste mit dem Namen definiert __all__, wird davon ausgegangen , dass es sich um die Liste der Modulnamen handelt, die importiert werden sollen, wenn ein Paketimport * auftritt. Es ist Sache des Paketautors, diese Liste auf dem neuesten Stand zu halten, wenn eine neue Version des Pakets veröffentlicht wird. Paketautoren entscheiden sich möglicherweise auch dafür, es nicht zu unterstützen, wenn sie keine Verwendung für den Import * aus ihrem Paket sehen. Beispielsweise sounds/effects/__init__.pykönnte die Datei den folgenden Code enthalten:

__all__ = ["echo", "surround", "reverse"]

Dies würde bedeuten, dass from sound.effects import *die drei genannten Submodule des Soundpakets importiert würden.

Gimel
quelle
3

Dies ist der beste Weg, den ich bisher gefunden habe:

from os.path import dirname, join, isdir, abspath, basename
from glob import glob
pwd = dirname(__file__)
for x in glob(join(pwd, '*.py')):
    if not x.startswith('__'):
        __import__(basename(x)[:-3], globals(), locals())
Farsheed
quelle
2

Mit importlibdas einzige , was du hast hinzuzufügen ist

from importlib import import_module
from pathlib import Path

__all__ = [
    import_module(f".{f.stem}", __package__)
    for f in Path(__file__).parent.glob("*.py")
    if "__" not in f.stem
]
del import_module, Path
ted
quelle
Dies führt zu einem legitimen Mypy-Problem : error: Type of __all__ must be "Sequence[str]", not "List[Module]". Eine Definition __all__ist nicht erforderlich, wenn dieser import_modulebasierte Ansatz verwendet wird.
Acumenus
1

Schauen Sie sich das pkgutil-Modul aus der Standardbibliothek an. Damit können Sie genau das tun, was Sie wollen, solange Sie eine __init__.pyDatei im Verzeichnis haben. Die __init__.pyDatei kann leer sein.

Mihail Mihaylov
quelle
1

Ich habe dafür ein Modul erstellt, das nicht auf __init__.py(oder einer anderen Zusatzdatei) basiert und mich nur die folgenden zwei Zeilen eingeben lässt:

import importdir
importdir.do("Foo", globals())

Fühlen Sie sich frei, wiederzuverwenden oder beizutragen: http://gitlab.com/aurelien-lourot/importdir

Aurelien
quelle
0

Importieren Sie sie einfach per importlib und fügen Sie sie __all__( addAktion ist optional) rekursiv im __init__.pyof-Paket hinzu.

/Foo
    bar.py
    spam.py
    eggs.py
    __init__.py

# __init__.py
import os
import importlib
pyfile_extes = ['py', ]
__all__ = [importlib.import_module('.%s' % filename, __package__) for filename in [os.path.splitext(i)[0] for i in os.listdir(os.path.dirname(__file__)) if os.path.splitext(i)[1] in pyfile_extes] if not filename.startswith('__')]
del os, importlib, pyfile_extes
Cheney
quelle
Wo ist pyfile_extes definiert?
Jeppe
Entschuldigung, dass ich es verpasst habe, jetzt behoben. Es ist die Erweiterung der Python-Datei, die Sie importieren möchten, normalerweise nurpy
Cheney
0

Wenn from . import *es nicht gut genug ist, ist dies eine Verbesserung gegenüber der Antwort von Ted . Insbesondere ist die Verwendung von __all__bei diesem Ansatz nicht erforderlich.

"""Import all modules that exist in the current directory."""
# Ref https://stackoverflow.com/a/60861023/
from importlib import import_module
from pathlib import Path

for f in Path(__file__).parent.glob("*.py"):
    module_name = f.stem
    if (not module_name.startswith("_")) and (module_name not in globals()):
        import_module(f".{module_name}", __package__)
    del f, module_name
del import_module, Path

Beachten Sie, dass module_name not in globals()das Modul nicht erneut importiert werden soll, wenn es bereits importiert wurde, da dies zu zyklischen Importen führen kann.

Scharfsinn
quelle