Come caricare tutti i moduli in una cartella?


269

Qualcuno potrebbe fornirmi un buon modo di importare un'intera directory di moduli?
Ho una struttura come questa:

/Foo
    bar.py
    spam.py
    eggs.py

Ho provato a convertirlo in un pacchetto aggiungendo __init__.pye facendo from Foo import *ma non ha funzionato come speravo.


2
Il modo init .py è abbastanza corretto. Puoi pubblicare il codice che non ha funzionato?
balpha,

9
Puoi definire "non ha funzionato"? Quello che è successo? Che messaggio di errore hai ricevuto?
S.Lott

Questo è Pythonic o raccomandato? "Esplicito è meglio che implicito."
yangmillstheory,

Esplicito è davvero meglio. Ma ti piacerebbe davvero affrontare questi fastidiosi messaggi "non trovati" ogni volta che aggiungi un nuovo modulo. Penso che se si dispone di una directory del pacchetto contenente molte piccole funzioni del modulo, questo è il modo migliore per farlo. I miei presupposti sono: 1. i moduli sono piuttosto semplici 2. si usa il pacchetto per il
riordino del

Risposte:


400

Elenca tutti i .pyfile python ( ) nella cartella corrente e inseriscili come __all__variabili in__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')]

57
Fondamentalmente, quindi posso rilasciare i file Python in una directory senza ulteriore configurazione e farli eseguire da uno script in esecuzione altrove.
Evan Fosmark,

5
@NiallDouglas questa risposta è per una domanda specifica che OP ha posto, non aveva un file zip e i file pyc possono essere inclusi facilmente, e stai dimenticando anche .pyd o .so libs ecc.
Anurag Uniyal

35
L'unica cosa che aggiungerei è if not os.path.basename(f).startswith('_')o almeno if not f.endswith('__init__.py')alla fine della comprensione della lista
Pykler,

10
Per renderlo più robusto, anche assicurarsi os.path.isfile(f)è True. Ciò somedir.py/
filtrerebbe i collegamenti

12
Aggiungere from . import *dopo impostazione __all__se si desidera moduli siano disponibili utilizzando .(per esempio come module.submodule1, module.submodule2e così via).
ostrokach,

133

Aggiungi la __all__variabile a __init__.pycontenente:

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

Vedi anche http://docs.python.org/tutorial/modules.html


163
Sì, sì, ma esiste un modo per renderlo dinamico?
Evan Fosmark,

27
Combinazione di os.listdir(), alcuni filtri, stripping di .pyestensione e __all__.

Non funziona per me come codice di esempio qui github.com/namgivu/python-import-all/blob/master/error_app.py . Forse mi manca qualcosa lì?
Nam G VU,

1
Ho scoperto me stesso - per usare la variabile / oggetto definito in quei moduli, dobbiamo usare il percorso di riferimento completo, ad es moduleName.varName. Rif. stackoverflow.com/a/710603/248616
Nam G VU,

1
@NamGVU: questo codice nella mia risposta a una domanda correlata importerà tutti i nomi dei sottomoduli pubblici nello spazio dei nomi del pacchetto.
martineau,

54

Aggiornamento nel 2017: probabilmente si desidera utilizzare importlibinvece.

Rendi la directory Foo un pacchetto aggiungendo un __init__.py. In quello __init__.pyaggiungi:

import bar
import eggs
import spam

Dal momento che lo vuoi dinamico (che può essere o non essere una buona idea), elenca tutti i file PY con dir elenco e importali con qualcosa del genere:

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

Quindi, dal tuo codice fai questo:

import Foo

Ora puoi accedere ai moduli con

Foo.bar
Foo.eggs
Foo.spam

ecc. from Foo import *non è una buona idea per diversi motivi, tra cui gli scontri con i nomi e rendendo difficile l'analisi del codice.


1
Non male, ma non dimenticare che puoi importare anche file .pyc e .pyo.
Evan Fosmark,

7
tbh, trovo __import__hackish, penso che sarebbe meglio aggiungere i nomi __all__e poi metterli from . import *in fondo alla sceneggiatura
freeforall tousez

1
Penso che sia più bello della versione glob.
lpapp,

8
__import__non è per usi generali, è usato da interpreter, usa importlib.import_module()invece.
Andrew_1510

2
Il primo esempio è stato molto utile, grazie! Sotto Python 3.6.4 dovevo fare from . import eggsecc. __init__.pyPrima che Python potesse importare. Con solo import eggsottengo ModuleNotFoundError: No module named 'eggs'quando provo a import Foonella main.pydirectory sopra.
Nick,

41

Espandendo la risposta di Mihail, credo che il modo non hacker (come in, non gestire direttamente i percorsi dei file) sia il seguente:

  1. crea un __init__.pyfile vuoto sottoFoo/
  2. Eseguire
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')

Otterrai:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>

Questo è soprattutto il modo per una risposta corretta: gestisce gli archivi ZIP, ma non scrive init né importa. Vedi automodinit di seguito.
Niall Douglas

1
Un'altra cosa: l'esempio sopra non controlla sys.modules per vedere se il modulo è già caricato. Senza questo controllo, quanto sopra caricherà il modulo una seconda volta :)
Niall Douglas

3
Quando eseguo load_all_modules_from_dir ('Foo / bar') con il tuo codice ottengo "RuntimeWarning: modulo padre 'Foo / bar' non trovato durante la gestione dell'importazione assoluta" - per sopprimerlo, devo impostare full_package_name = '.'. Join ( dirname.split (os.path.sep) + nome_pacchetto]) e importare anche Foo.bar
Alex Dupuy

Quei RuntimeWarningmessaggi possono anche essere evitati non usando full_package_name a tutti: importer.find_module(package_name).load_module(package_name).
Artfunkel,

Gli RuntimeWarningerrori possono anche essere evitati (in modo discutibilmente brutto) importando il genitore (dirname AKA). Un modo per farlo è - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Naturalmente, funziona solo se si dirnametratta di un percorso relativo monocomponente; senza tagli. Personalmente, preferisco l'approccio di @ Artfunkel all'utilizzo del nome_pacchetto base.
dannysauer,

32

Python, includi tutti i file in una directory:

Per i neofiti che non riescono a farlo funzionare e che hanno bisogno delle loro mani.

  1. Crea una cartella / home / el / foo e crea un file main.pyin / home / el / foo Inserisci questo codice:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Crea una directory /home/el/foo/hellokitty

  3. Crea un file __init__.pysotto /home/el/foo/hellokittye inserisci questo codice:

    __all__ = ["spam", "ham"]
  4. Crea due file Python: spam.pye ham.pysotto/home/el/foo/hellokitty

  5. Definire una funzione all'interno di spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Definire una funzione all'interno di ham.py:

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

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney

1
Non solo per i neofiti, ma hanno anche sperimentato gli sviluppatori Python a cui piacciono le risposte chiare. Grazie.
Michael Scheper,

Ma l'utilizzo import *è considerato una cattiva pratica di codifica Python. Come si fa a farlo senza quello?
Rachael Blake,

16

Mi sono stancato di questo problema, quindi ho scritto un pacchetto chiamato automodinit per risolverlo. Puoi ottenerlo da http://pypi.python.org/pypi/automodinit/ .

L'utilizzo è così:

  1. Includi il automodinitpacchetto nelle tue setup.pydipendenze.
  2. Sostituisci tutti i file __init__.py in questo modo:
__all__ = ["Verrò riscritto"]
# Non modificare la riga sopra o questa riga!
import automodinit
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Tutto quello che vuoi può andare dopo, non verrà modificato.

Questo è tutto! D'ora in poi l'importazione di un modulo imposterà __all__ su un elenco di file .py [co] nel modulo e importerà anche ciascuno di quei file come se si fosse digitato:

for x in __all__: import x

Pertanto l'effetto di "from M import *" corrisponde esattamente a "import M".

automodinit è felice di correre dall'interno degli archivi ZIP ed è quindi sicuro ZIP.

Niall


pip non può scaricare automodinit perché non è stato caricato nulla su pypi per questo.
kanzure,

Grazie per la segnalazione di bug su github. Ho risolto questo problema in v0.13. Niall
Niall Douglas,

11

So che sto aggiornando un post piuttosto vecchio, e ho provato a usarlo automodinit, ma ho scoperto che il processo di installazione è interrotto per python3. Quindi, in base alla risposta di Luca, ho trovato una risposta più semplice - che potrebbe non funzionare con .zip - a questo problema, quindi ho pensato di condividerla qui:

all'interno del __init__.pymodulo da yourpackage:

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

e all'interno di un altro pacchetto di seguito yourpackage:

from yourpackage import *

Quindi avrai tutti i moduli inseriti nel pacchetto caricato e, se scrivi un nuovo modulo, verrà importato anche automagicamente. Naturalmente, usa quel tipo di cose con cura, con grandi poteri derivano grandi responsabilità.


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

5

Ho riscontrato anche questo problema e questa era la mia soluzione:

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()

Questa funzione crea un file (nella cartella fornita) denominato __init__.py, che contiene una __all__variabile che contiene tutti i moduli nella cartella.

Ad esempio, ho una cartella denominata Test che contiene:

Foo.py
Bar.py

Quindi nello script voglio importare i moduli in cui scriverò:

loadImports('Test/')
from Test import *

Ciò importerà tutto da Teste il __init__.pyfile in Testora conterrà:

__all__ = ['Foo','Bar']

perfetto, esattamente quello che sto cercando
uma mahesh

4

L'esempio di Anurag con un paio di correzioni:

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

4

Anurag Uniyal risponde con i miglioramenti suggeriti!

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

3

Vedi che il tuo __init__.pydefinisce __all__. I moduli - dice doc

I __init__.pyfile sono necessari per fare in modo che Python tratti le directory come pacchetti contenenti; ciò viene fatto per impedire alle directory con un nome comune, come stringa, di nascondere involontariamente moduli validi che si verificano successivamente nel percorso di ricerca del modulo. Nel caso più semplice, __init__.pypuò essere solo un file vuoto, ma può anche eseguire il codice di inizializzazione per il pacchetto o impostare la __all__variabile, descritta più avanti.

...

L'unica soluzione è che l'autore del pacchetto fornisca un indice esplicito del pacchetto. L'istruzione import utilizza la seguente convenzione: se il __init__.pycodice di un pacchetto definisce un elenco denominato __all__, viene considerato l'elenco dei nomi dei moduli che devono essere importati quando si incontra dall'importazione pacchetto *. Spetta all'autore del pacchetto mantenere aggiornato questo elenco quando viene rilasciata una nuova versione del pacchetto. Gli autori dei pacchetti possono anche decidere di non supportarlo, se non vedono un uso per l'importazione * dal loro pacchetto. Ad esempio, il file sounds/effects/__init__.pypotrebbe contenere il seguente codice:

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

Ciò significherebbe from sound.effects import *importare i tre sottomoduli nominati del pacchetto audio.


3

Questo è il modo migliore che ho trovato finora:

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())

2

L' importlibunica cosa che devi aggiungere è

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

Questo porta ad un problema mypy legittima: error: Type of __all__ must be "Sequence[str]", not "List[Module]". La definizione __all__non è richiesta se import_modulesi utilizza questo approccio basato.
Acumenus,

1

Guarda il modulo pkgutil dalla libreria standard. Ti consentirà di fare esattamente quello che vuoi purché tu abbia un __init__.pyfile nella directory. Il __init__.pyfile può essere vuoto.


1

Ho creato un modulo per questo, che non si basa su __init__.py(o su qualsiasi altro file ausiliario) e mi fa scrivere solo le seguenti due righe:

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

Sentiti libero di riutilizzare o contribuire: http://gitlab.com/aurelien-lourot/importdir


0

Basta importarli da importlib e aggiungerli a __all__( addazione è facoltativa) come riferimento nel __init__.pypacchetto di.

/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

Dove è definito pyfile_extes?
Jeppe,

scusa per averlo perso, ora risolto. È l'estensione del file Python che vuoi importare, di solito solopy
Cheney,

0

Quando from . import *non è abbastanza buono, questo è un miglioramento rispetto alla risposta di Ted . In particolare, l'uso di __all__non è necessario con questo approccio.

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

Si noti che module_name not in globals()è destinato a evitare di reimportare il modulo se è già stato importato, poiché ciò può rischiare di importare ciclicamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.