2
votes

Comment importer dynamiquement tous les fichiers * .py d'un répertoire donné ET de tous les sous-répertoires?

Supposons que j'ai un tas de fichiers Python sous un répertoire commun, mais à l'intérieur de celui-ci, un nombre arbitraire de sous-répertoires peut exister:

import_everything_under('first')

J'ai des fichiers arbitraires où je veux importer tous ces éléments. Manuellement, je pourrais faire:

import first.foo
import first.bar
import first.fizz.buzz
import first.numbers.one
import first.numbers.two

Mais à la place, j'aimerais pouvoir faire quelque chose comme:

/first/foo.py
/first/bar.py
/first/fizz/buzz.py
/first/numbers/one.py
/first/numbers/two.py

Je sais qu'une question similaire a déjà surgi: Importez récursivement tous les fichiers .py fichiers de tous les dossiers

Mais les réponses données et les doublons présumés ne répondent PAS à cette question.


Cette question a été signalée comme un doublon possible de: Comment charger tous les modules d'un dossier?

Encore une fois, cela ne répond PAS à cette question. La réponse donnée à cette question n'est pas récursive - elle importera UNIQUEMENT des éléments du répertoire immédiat et n'inclut PAS les scripts dans les sous-répertoires, dont j'ai besoin pour mon cas d'utilisation.


3 commentaires

Copie possible de Comment charger tous les modules dans un dossier?


Pourquoi voudriez-vous faire ça? Qu'en est-il de la création de d'abord / __ init__.py puis d'y importer tous les modules pertinents (+ de même pour tous les sous-modules)?


@a_guest Je sais que c'est certainement un cas d'utilisation inhabituel, mais cela est dû à la façon dont un cadre de dépendance que j'utilise est mis en place. Essentiellement, il permet à une sorte de système de "plugin" où il charge des scripts d'affecter son comportement à partir d'un répertoire spécifique - mais UNIQUEMENT ce répertoire. Il n'autorise pas les sous-répertoires, ce qui rend l'organisation à grande échelle très pénible. Je veux donc un seul script dans ce répertoire principal qui puisse charger dynamiquement mes propres scripts à partir de toute structure de répertoire arbitraire que je définis. Je pourrais simplement importer "manuellement" tous les modules dont j'ai besoin, mais c'est aussi une corvée de maintenance.


3 Réponses :


0
votes

Je suis désolé. Je ne sais pas si c'est bien de cette façon. Mais essayez ça.

de la première importation *


1 commentaires

Cela n'importera pas récursivement à partir de sous-répertoires et ne répond donc pas aux besoins de cette question.



1
votes

Votre problème se décompose en deux étapes (de mon point de vue):

  1. Parcourez vos répertoires et sous-répertoires et trouvez les noms de tous les fichiers .py que vous souhaitez importer

  2. Importez-les!

Pour résoudre (1), nous pouvons utiliser la méthode os.walk pour trouver tous les fichiers .py. Cette fonction ressemblerait à ceci:

import os
import importlib


def get_py_files(src):
    cwd = os.getcwd() # Current Working directory
    py_files = [] 
    for root, dirs, files in os.walk(src):
        for file in files:
            if file.endswith(".py"):
                py_files.append(os.path.join(cwd, root, file))
    return py_files


def dynamic_import(module_name, py_path):
    module_spec = importlib.util.spec_from_file_location(module_name, py_path)
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module


def dynamic_import_from_src(src, star_import = False):
    my_py_files = get_py_files(src)
    for py_file in my_py_files:
        module_name = os.path.split(py_file)[-1].strip(".py")
        imported_module = dynamic_import(module_name, py_file)
        if star_import:
            for obj in dir(imported_module):
                globals()[obj] = imported_module.__dict__[obj]
        else:
            globals()[module_name] = imported_module
    return

if __name__ == "__main__":
    dynamic_import_from_src("first", star_import = False)

Maintenant que vous avez une liste de vos fichiers .py, nous avons besoin d'une fonction qui peut les importer dynamiquement.

def dynamic_import_from_src(src, star_import = False):
    my_py_files = get_py_files(src)
    for py_file in my_py_files:
        module_name = os.path.split(py_file)[-1].strip(".py")
        imported_module = dynamic_import(module_name, py_file)
        if star_import:
            for obj in dir(imported_module):
                globals()[obj] = imported_module.__dict__[obj]
        else:
            globals()[module_name] = imported_module
    return

Et maintenant nous avons juste besoin de tout rassembler, en écrivant une fonction qui appelle votre fonction get_py_files , puis en boucle sur les résultats, en chargeant le modules avec dynamic_import .

Je suppose que vous voulez que les noms de module que vous utilisez dans votre script python soient les mêmes que le nom de fichier, mais vous pouvez changer cela en modifiant le module_name variable dans la fonction ci-dessous.

import importlib
def dynamic_import(module_name, py_path):
    module_spec = importlib.util.spec_from_file_location(module_name, py_path)
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module

Notez que nous devons appeler globals () pour ajouter le module à l'espace de noms global. Sans cela, le module sera importé mais vous n'aurez aucun moyen d'accéder à quoi que ce soit à l'intérieur. Vous pouvez passer star_import = True à dynamic_import_from_src si vous souhaitez plutôt qu'il s'agisse d'une importation en étoile. (comme from first.foo import * . Notez que cela peut écraser des variables dans votre espace de noms, mais c'est l'un des inconvénients de l'utilisation de l'importation * de toute façon.

Tout jeter dans un seul bloc pour qu'il soit plus facile de tout voir à la fois:

import os
def get_py_files(src):
    cwd = os.getcwd() # Current Working directory
    py_files = [] 
    for root, dirs, files in os.walk(src):
        for file in files:
            if file.endswith(".py"):
                py_files.append(os.path.join(cwd, root, file))
    return py_files

À ce stade, vous pouvez accéder à tous les modules que vous avez importés exactement de la même manière comme si vous aviez importé en premier.quelque soit . Par exemple, si first / numbers / one.py contenait x = 1 , alors vous seriez capable d'y accéder en disant one.x.


3 commentaires

Je vous remercie! Cela semble fonctionner. Marqué comme accepté.


@ glenn.barker J'ai apporté quelques modifications. J'ai accidentellement écrasé le mot-clé intégré, dir dans certaines fonctions, alors je l'ai remplacé par src à la place. Ajout de la possibilité de simuler les importations * dans dynamic_import_from_src


Merci beaucoup. Vraiment apprécié. J'ai également trouvé ma propre solution pendant que vous tapiez la vôtre, alors j'ai également partagé ce que j'ai trouvé comme réponse séparée. Je pense que l'une ou l'autre solution devrait fonctionner ici, nous devrons donc inclure les deux pour référence future. :-)



2
votes

La solution de @SyntaxVoid devrait fonctionner mais en parallèle j'ai bricolé moi-même ce problème et je pense avoir trouvé une autre façon de le faire. Soit ma propre solution, soit la solution de @ SyntaxVoid devrait fonctionner, je crois.

from importlib import util
from os import path
from glob import glob


def import_submodules(start_path, include_start_directory=True):
    start_path = path.abspath(start_path)
    pattern = '**/*.py' if include_start_directory else '*/**/*.py'
    py_files = [f for f in glob(path.join(start_path, pattern), recursive=True) if not f.endswith('__.py')]

    for py_file in py_files:
        spec = util.spec_from_file_location('', py_file)
        module = util.module_from_spec(spec)
        spec.loader.exec_module(module)


0 commentaires