Comment charger tous les modules dans un dossier?

269

Quelqu'un pourrait-il me fournir un bon moyen d'importer tout un répertoire de modules?
J'ai une structure comme celle-ci:

/Foo
    bar.py
    spam.py
    eggs.py

J'ai essayé de le convertir en package en ajoutant __init__.pyet en faisant, from Foo import *mais cela n'a pas fonctionné comme je l'espérais.

Evan Fosmark
la source
2
La méthode init .py est assez correcte. Pouvez-vous publier le code qui n'a pas fonctionné?
balpha
9
Pouvez-vous définir «n'a pas fonctionné»? Qu'est-il arrivé? Quel message d'erreur avez-vous reçu?
S.Lott
Est-ce Pythonic ou recommandé? "Explicite vaut mieux qu'implicite."
yangmillstheory
Explicite est en effet mieux. Mais voudriez-vous vraiment adresser ces messages ennuyeux «non trouvés» à chaque fois que vous ajoutez un nouveau module. Je pense que si vous avez un répertoire de packages contenant de nombreuses petites fonctions de module, alors c'est la meilleure façon de procéder. Mes hypothèses sont les suivantes: 1. le module est plutôt simple 2. vous utilisez le package pour le
rangement du

Réponses:

400

Répertoriez tous les .pyfichiers python ( ) dans le dossier actuel et placez-les comme __all__variables dans__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
la source
57
Fondamentalement, je peux déposer des fichiers python dans un répertoire sans autre configuration et les faire exécuter par un script exécuté ailleurs.
Evan Fosmark
5
@NiallDouglas cette réponse est pour une question spécifique posée par OP, il n'avait pas de fichier zip et les fichiers pyc peuvent être inclus facilement, et vous oubliez les bibliothèques .pyd ou .so etc
Anurag Uniyal
35
La seule chose que j'ajouterais est if not os.path.basename(f).startswith('_')ou à tout le moins if not f.endswith('__init__.py')à la fin de la compréhension de la liste
Pykler
10
Pour le rendre plus robuste, également vous assurer os.path.isfile(f)est True. Cela filtrerait les liens symboliques et les répertoires cassés comme somedir.py/(coin-cas, je l'admets, mais quand même ...)
MestreLion
12
Ajouter from . import *après le réglage __all__si vous voulez être à sous - modules disponibles à l' aide .(par exemple , comme module.submodule1, module.submodule2, etc.).
ostrokach
133

Ajoutez la __all__variable à __init__.pycontenant:

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

Voir aussi http://docs.python.org/tutorial/modules.html

Stefanw
la source
163
Oui, oui, mais y a-t-il moyen de le faire être dynamique?
Evan Fosmark
27
Combinaison de os.listdir(), certains filtrage, décapage d' .pyextension et __all__.
Ne fonctionne pas pour moi comme exemple de code ici github.com/namgivu/python-import-all/blob/master/error_app.py . Peut-être que je manque quelque chose là-bas?
Nam G VU
1
Je me suis découvert - pour utiliser la variable / l'objet défini dans ces modules, nous devons utiliser le chemin de référence complet, par exemple moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: Ce code dans ma réponse à une question connexe importera tous les noms des sous-modules publics dans l'espace de noms du package.
martineau
54

Mise à jour en 2017: vous voudrez probablement l'utiliser à la importlibplace.

Faites du répertoire Foo un package en ajoutant un __init__.py. Dans cet __init__.pyajout:

import bar
import eggs
import spam

Puisque vous le voulez dynamique (ce qui peut être une bonne idée ou non), listez tous les fichiers py avec list dir et importez-les avec quelque chose comme ceci:

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

Ensuite, à partir de votre code, procédez comme suit:

import Foo

Vous pouvez maintenant accéder aux modules avec

Foo.bar
Foo.eggs
Foo.spam

etc. from Foo import *n'est pas une bonne idée pour plusieurs raisons, y compris les conflits de noms et rendant difficile l'analyse du code.

Lennart Regebro
la source
1
Pas mal, mais n'oubliez pas que vous pouvez également importer des fichiers .pyc et .pyo.
Evan Fosmark
7
tbh, je trouve __import__hackish, je pense qu'il vaudrait mieux ajouter les noms __all__puis mettre from . import *en bas du script
freeforall tousez
1
Je pense que c'est plus agréable que la version glob.
lpapp
8
__import__n'est pas à usage général, il est utilisé par interpreter, utilisez importlib.import_module()plutôt.
Andrew_1510
2
Le premier exemple a été très utile, merci! Sous Python 3.6.4, je devais faire from . import eggsetc. __init__.pyavant que Python puisse importer. Avec seulement import eggsje reçois ModuleNotFoundError: No module named 'eggs'en essayant de import Foodans le main.pydans le répertoire ci - dessus.
Nick
41

S'étendant sur la réponse de Mihail, je crois que la méthode non piratée (comme dans, ne pas gérer directement les chemins de fichiers) est la suivante:

  1. créer un __init__.pyfichier vide sousFoo/
  2. Exécuter
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')

Tu auras:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
la source
C'est la plupart du temps là pour une réponse correcte - il gère les archives ZIP, mais n'écrit pas init ni import. Voir automodinit ci-dessous.
Niall Douglas
1
Autre chose: l'exemple ci-dessus ne vérifie pas sys.modules pour voir si le module est déjà chargé. Sans cette vérification, ce qui précède chargera le module une deuxième fois :)
Niall Douglas
3
Lorsque j'exécute load_all_modules_from_dir ('Foo / bar') avec votre code, j'obtiens "RuntimeWarning: Module parent 'Foo / bar' non trouvé lors de la gestion de l'importation absolue" - pour supprimer cela, je dois définir full_package_name = '.'. Join ( dirname.split (os.path.sep) + package_name]) et également importer Foo.bar
Alex Dupuy
Ces RuntimeWarningmessages peuvent également être évités en ne pas utiliser du tout full_package_name: importer.find_module(package_name).load_module(package_name).
Artfunkel
Les RuntimeWarningerreurs peuvent également être évitées (d'une manière peut-être laide) en important le parent (AKA dirname). Une façon de le faire est - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Bien sûr, cela ne fonctionne que s'il dirnames'agit d'un chemin relatif à un seul composant; pas de barres obliques. Personnellement, je préfère l'approche de @ Artfunkel d'utiliser à la place le package_name de base.
dannysauer
32

Python, incluez tous les fichiers dans un répertoire:

Pour les débutants qui ne peuvent tout simplement pas se mettre au travail et qui ont besoin de tenir leurs mains.

  1. Créez un dossier / home / el / foo et créez un fichier main.pysous / home / el / foo Mettez ce code là:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Créer un répertoire /home/el/foo/hellokitty

  3. Faites un fichier __init__.pysous /home/el/foo/hellokittyet mettez ce code dedans:

    __all__ = ["spam", "ham"]
  4. Créez deux fichiers python: spam.pyet ham.pysous/home/el/foo/hellokitty

  5. Définissez une fonction dans spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Définissez une fonction dans ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Exécuter:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
Eric Leschinski
la source
1
Pas seulement pour les débutants, mais aussi pour les développeurs Python expérimentés qui aiment les réponses claires. Merci.
Michael Scheper
Mais l'utilisation import *est considérée comme une mauvaise pratique de codage Python. Comment faites-vous cela sans cela?
Rachael Blake
16

Je me suis fatigué de ce problème moi-même, j'ai donc écrit un paquet appelé automodinit pour le corriger. Vous pouvez l'obtenir sur http://pypi.python.org/pypi/automodinit/ .

L'utilisation est comme ceci:

  1. Incluez le automodinitpackage dans vos setup.pydépendances.
  2. Remplacez tous les fichiers __init__.py comme ceci:
__all__ = ["Je vais être réécrit"]
# Ne modifiez pas la ligne ci-dessus, ou cette ligne!
importer automodinit
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Tout ce que vous voulez peut être recherché ici, il ne sera pas modifié.

C'est tout! Désormais, l'importation d'un module définira __all__ sur une liste de fichiers .py [co] dans le module et importera également chacun de ces fichiers comme si vous aviez tapé:

for x in __all__: import x

Par conséquent, l'effet de "from M import *" correspond exactement à "import M".

automodinit est heureux de fonctionner à l'intérieur des archives ZIP et est donc protégé par ZIP.

Niall

Niall Douglas
la source
pip ne peut pas télécharger automodinit car il n'y a rien de téléchargé sur pypi pour cela.
kanzure
Merci pour le rapport de bug sur github. J'ai corrigé cela dans la v0.13. Niall
Niall Douglas
11

Je sais que je mets à jour un article assez ancien, et j'ai essayé de l'utiliser automodinit, mais j'ai découvert que son processus de configuration était interrompu pour python3. Donc, basé sur la réponse de Luca, j'ai trouvé une réponse plus simple - qui pourrait ne pas fonctionner avec .zip - à ce problème, alors j'ai pensé que je devrais la partager ici:

dans le __init__.pymodule à partir de yourpackage:

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

et dans un autre package ci yourpackage- dessous :

from yourpackage import *

Ensuite, tous les modules placés dans le package seront chargés, et si vous écrivez un nouveau module, il sera également importé automatiquement. Bien sûr, utiliser ce genre de choses avec précaution, avec de grands pouvoirs entraîne de grandes responsabilités.

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

J'ai également rencontré ce problème et c'était ma solution:

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

Cette fonction crée un fichier (dans le dossier fourni) nommé __init__.py, qui contient une __all__variable qui contient tous les modules du dossier.

Par exemple, j'ai un dossier nommé Test qui contient:

Foo.py
Bar.py

Donc, dans le script, je veux que les modules soient importés, j'écrirai:

loadImports('Test/')
from Test import *

Cela importera tout de Testet le __init__.pyfichier dans Testcontiendra désormais:

__all__ = ['Foo','Bar']
Penguinblade
la source
parfait, exactement ce que je cherche
uma mahesh
4

Exemple d'Anurag avec quelques corrections:

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
la source
4

Réponse d'Anurag Uniyal avec suggestions d'améliorations!

#!/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
la source
3

Voyez que votre __init__.pydéfinit __all__. Les modules - packages doc dit

Les __init__.pyfichiers sont nécessaires pour que Python traite les répertoires comme contenant des packages; cela est fait pour empêcher les répertoires avec un nom commun, tel que chaîne, de masquer involontairement des modules valides qui se produisent plus tard sur le chemin de recherche de module. Dans le cas le plus simple, il __init__.pypeut simplement s'agir d'un fichier vide, mais il peut également exécuter le code d'initialisation du package ou définir la __all__variable, décrite plus loin.

...

La seule solution consiste pour l'auteur du package à fournir un index explicite du package. L'instruction import utilise la convention suivante: si le __init__.pycode d' un package définit une liste nommée __all__, il s'agit de la liste des noms de module qui doivent être importés lors de l'importation du package *. Il appartient à l'auteur du package de maintenir cette liste à jour lorsqu'une nouvelle version du package est publiée. Les auteurs de packages peuvent également décider de ne pas le prendre en charge, s'ils ne voient pas d'utilisation pour importer * à partir de leur package. Par exemple, le fichier sounds/effects/__init__.pypeut contenir le code suivant:

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

Cela signifierait que from sound.effects import *cela importerait les trois sous-modules nommés du package audio.

gimel
la source
3

C'est le meilleur moyen que j'ai trouvé jusqu'à présent:

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
la source
2

Utiliser importlibla seule chose que vous devez ajouter est

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
la source
Cela conduit à un problème de mypy légitime: error: Type of __all__ must be "Sequence[str]", not "List[Module]". La définition __all__n'est pas requise si cette import_moduleapproche basée est utilisée.
Acumenus
1

Regardez le module pkgutil de la bibliothèque standard. Il vous permettra de faire exactement ce que vous voulez tant que vous avez un __init__.pyfichier dans le répertoire. Le __init__.pyfichier peut être vide.

Mihail Mihaylov
la source
1

J'ai créé un module pour cela, qui ne s'appuie pas sur __init__.py(ou tout autre fichier auxiliaire) et me fait taper uniquement les deux lignes suivantes:

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

N'hésitez pas à réutiliser ou à contribuer: http://gitlab.com/aurelien-lourot/importdir

Aurelien
la source
0

Importez -les simplement par importlib et ajoutez-les à __all__(l' addaction est facultative) dans recurse dans le __init__.pypackage.

/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
la source
Où pyfile_extes est-il défini?
Jeppe
désolé de l'avoir manquée, maintenant corrigé. C'est l'extension du fichier python que vous souhaitez importer, généralement justepy
Cheney
0

Quand ce from . import *n'est pas assez bon, c'est une amélioration par rapport à la réponse de ted . Plus précisément, l'utilisation de __all__n'est pas nécessaire avec cette approche.

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

Notez que cela module_name not in globals()vise à éviter de réimporter le module s'il est déjà importé, car cela peut risquer des importations cycliques.

Acumenus
la source