Comment faire pour importer tous les submodules?

J'ai une structure de répertoire comme suit:

| main.py
| scripts
|--| __init__.py
   | script1.py
   | script2.py
   | script3.py

Depuis main.py, le module scripts est importé. J'ai essayé d'utiliser pkgutils.walk_packages en combinaison avec __all__, mais en utilisant cela, je ne peux importer tous les sous-modules directement sous main en utilisant from scripts import *. Je voudrais les obtenir tous sous scripts. Quel serait le moyen le plus propre d'importer tous les sous-modules de scripts afin que je puisse accéder à scripts.script1 à partir de main?

EDIT: je suis désolé d'avoir été un peu vague. Je voudrais importer les submodules sur exécution sans les spécifier explicitement dans __init__.py. Je peux utiliser pkgutils.walk_packages pour obtenir les noms des sous-modules (sauf si quelqu'un connaît un meilleur moyen), mais je ne suis pas sûr de la façon la plus propre d'utiliser ces noms (ou peut-être les Impimporteurs que walk_packages renvoie?) pour les importer.

30
demandé sur linkmaster03 2010-07-29 22:18:44

9 réponses

Edit: Voici une façon d'importer récursivement tout à l'exécution...

(contenu de __init__.py dans le répertoire du paquet supérieur)

import pkgutil

__all__ = []
for loader, module_name, is_pkg in  pkgutil.walk_packages(__path__):
    __all__.append(module_name)
    module = loader.find_module(module_name).load_module(module_name)
    globals()[module_name] = module

Je n'utilise pas __import__(__path__+'.'+module_name) ici, car il est difficile d'importer correctement les paquets de manière récursive. Si vous n'avez pas de sous-paquets imbriqués et que vous voulez éviter d'utiliser globals()[module_name], c'est une façon de le faire.

Il y a probablement un meilleur moyen, mais c'est le mieux que je puisse faire, de toute façon.

Réponse originale (pour le contexte, ignorer othwerwise. J'ai mal compris la question initialement):

À quoi ressemble votre scripts/__init__.py? Cela devrait être quelque chose comme:

import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']

Vous pourriez même faire sans définir __all__, mais les choses (pydoc, si rien d'autre) fonctionneront plus proprement si vous le définissez, même si c'est juste une liste de ce que vous avez importé.

27
répondu Joe Kington 2018-08-07 11:24:15

Ceci est basé sur la réponse que kolypto a fournie , mais sa réponse n'effectue pas d'importation récursive de paquets, alors que c'est le cas. Bien que non requis par la question principale, je crois que l'importation récursive s'applique et peut être très utile dans de nombreuses situations similaires. Pour ma part, j'ai trouvé cette question lors de la recherche sur le sujet.

C'est une façon agréable et propre d'effectuer l'importation des modules du sous-paquet, et devrait également être portable, et il utilise la lib standard pour Python 2.7 + / 3.X.

import importlib
import pkgutil


def import_submodules(package, recursive=True):
    """ Import all submodules of a module, recursively, including subpackages

    :param package: package (name or actual module)
    :type package: str | module
    :rtype: dict[str, types.ModuleType]
    """
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = package.__name__ + '.' + name
        results[full_name] = importlib.import_module(full_name)
        if recursive and is_pkg:
            results.update(import_submodules(full_name))
    return results

Utilisation:

# from main.py, as per the OP's project structure
import scripts
import_submodules(scripts)

# Alternatively, from scripts.__init__.py
import_submodules(__name__)
21
répondu Mr. B 2017-05-23 12:02:20

Fonctionne simplement, et permet l'importation relative dans les paquets:

def import_submodules(package_name):
    """ Import all submodules of a module, recursively

    :param package_name: Package name
    :type package_name: str
    :rtype: dict[types.ModuleType]
    """
    package = sys.modules[package_name]
    return {
        name: importlib.import_module(package_name + '.' + name)
        for loader, name, is_pkg in pkgutil.walk_packages(package.__path__)
    }

Utilisation:

__all__ = import_submodules(__name__).keys()
11
répondu kolypto 2014-08-01 15:03:59

Pas aussi propre que je le voudrais, mais aucune des méthodes plus propres n'a fonctionné pour moi. Cela permet d'atteindre le comportement spécifié:

Structure du répertoire:

| pkg
|--| __init__.py
   | main.py
   | scripts
   |--| __init__.py
      | script1.py
      | script2.py
      | script3.py

pkg/scripts/__init__.py est vide, et pkg/__init__.py contient:

import importlib as _importlib
import pkgutil as _pkgutil
__all__ = [_mod[1].split(".")[-1] for _mod in
           filter(lambda _mod: _mod[1].count(".") == 1 and not 
                               _mod[2] and __name__ in _mod[1],
                  [_mod for _mod in _pkgutil.walk_packages("." + __name__)])]
__sub_mods__ = [".".join(_mod[1].split(".")[1:]) for _mod in
                filter(lambda _mod: _mod[1].count(".") > 1 and not 
                                    _mod[2] and __name__ in _mod[1],
                       [_mod for _mod in 
                        _pkgutil.walk_packages("." + __name__)])]
from . import *
for _module in __sub_mods__:
    _importlib.import_module("." + _module, package=__name__)

Bien qu'il soit désordonné, il devrait être portable. J'ai utilisé ce code pour plusieurs paquets différents.

2
répondu user2561747 2015-04-10 17:46:41

Je me suis fatigué de ce problème moi-même, alors j'ai écrit un paquet appelé automodinit pour le réparer. Vous pouvez l'obtenir à partir de http://pypi.python.org/pypi/automodinit/. L'utilisation est comme ceci:

  1. incluez le paquet automodinit dans vos dépendances setup.py.
  2. ajoutez ce qui suit Au début du fichier __init__.py:
__all__ = ["I will get rewritten"]
# Don't modify the line above, or this line!
import automodinit
automodinit.automodinit(__name__, __file__, globals())
del automodinit
# Anything else you want can go after here, it won't get modified.

C'est ça! À partir de Maintenant, l'importation d'un module définira __all__ sur une liste de fichiers. PY [co] dans le module et importera également chaque 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 courir à L'intérieur des archives ZIP et est donc ZIP safe.

2
répondu Niall Douglas 2017-12-28 20:31:58

J'écrivais une petite bibliothèque personnelle et j'ajoutais tout le temps de nouveaux modules, donc j'ai écrit un script shell pour rechercher des scripts et créer les __init__.py. le script est exécuté juste en dehors du répertoire principal de mon paquet, pylux.

Je sais que ce n'est probablement pas la réponse que vous cherchez, mais cela a servi son but pour moi et cela pourrait être utile à quelqu'un d'autre.

#!/bin/bash

echo 'Traversing folder hierarchy...'

CWD=`pwd`


for directory in `find pylux -type d -exec echo {} \;`;
do
    cd $directory
    #echo Entering $directory
    echo -n "" > __init__.py

    for subdirectory in `find . -type d -maxdepth 1 -mindepth 1`;
    do
        subdirectory=`echo $subdirectory | cut -b 3-`
        #echo -n '    ' ...$subdirectory
        #echo -e '\t->\t' import $subdirectory
        echo import $subdirectory >> __init__.py
    done

    for pyfile in *.py ;
    do
        if [ $pyfile = $(echo __init__.py) ]; then
            continue
        fi
        #echo -n '    ' ...$pyfile
        #echo -e '\t->\t' import `echo $pyfile | cut -d . -f 1`
        echo import `echo $pyfile | cut -d . -f 1` >> __init__.py
    done
    cd $CWD

done


for directory in `find pylux -type d -exec echo {} \;`;
do
    echo $directory/__init__.py:
    cat $directory/__init__.py | awk '{ print "\t"$0 }'
done
1
répondu physicsmichael 2010-07-29 18:59:18

J'ai joué avec la réponse de Joe Kington et j'ai construit une solution qui utilise globals et get/setattr et n'a donc pas besoin d'eval. Une légère modification est qu'au lieu d'utiliser directement les paquets __path__ pour walk_packages, j'utilise le répertoire parent des paquets et n'importe que les modules commençant par __name__ + ".". Cela a été fait pour obtenir de manière fiable tous les sous-paquets de walk_packages - dans mon cas d'utilisation, j'avais un sous-paquet nommé test qui a provoqué l'itération de pkgutil sur le paquet test de python de plus, l'utilisation de __path__ ne se reproduirait pas dans les sous-répertoires des paquets. Tous ces problèmes ont été observés en utilisant jython et python2.5, le code ci-dessous n'est testé que dans jython jusqu'à présent.

Notez également que la question OPs ne parle que de l'importation de tous les modules à partir d'un paquet, ce code importe récursivement tous les paquets aussi.

from pkgutil import walk_packages
from os import path

__all__ = []
__pkg_prefix = "%s." % __name__
__pkg_path = path.abspath(__path__[0]).rsplit("/", 1)[0] #parent directory

for loader, modname, _ in walk_packages([__pkg_path]):
    if modname.startswith(__pkg_prefix):
        #load the module / package
        module = loader.find_module(modname).load_module(modname)
        modname = modname[len(__pkg_prefix):] #strip package prefix from name
        #append all toplevel modules and packages to __all__
        if not "." in modname:
            __all__.append(modname)
            globals()[modname] = module
        #set everything else as an attribute of their parent package
        else:
            #get the toplevel package from globals()
            pkg_name, rest = modname.split(".", 1)
            pkg = globals()[pkg_name]
            #recursively get the modules parent package via getattr
            while "." in rest:
                subpkg, rest = rest.split(".", 1)
                pkg = getattr(pkg, subpkg)
            #set the module (or package) as an attribute of its parent package
            setattr(pkg, rest, module)

Comme amélioration future, je vais essayer de rendre cette dynamique avec un crochet __getattr__ sur le paquet, donc les modules réels ne sont que importés quand ils sont accessibles...

1
répondu l4mpi 2017-05-23 12:34:23

Cela fonctionne bien pour moi en Python 3.3. Notez que cela ne fonctionne que pour les sous-modules qui sont dans des fichiers dans le même répertoire que le __init__.py. Avec un peu de travail cependant, il peut être amélioré pour supporter les sous-modules dans les répertoires aussi.

from glob import iglob
from os.path import basename, relpath, sep, splitext

def import_submodules(__path__to_here):
    """Imports all submodules.
    Import this function in __init__.py and put this line to it:
    __all__ = import_submodules(__path__)"""
    result = []
    for smfile in iglob(relpath(__path__to_here[0]) + "/*.py"):
        submodule = splitext(basename(smfile))[0]
        importstr = ".".join(smfile.split(sep)[:-1])
        if not submodule.startswith("_"):
            __import__(importstr + "." + submodule)
            result.append(submodule)
    return result
0
répondu SzieberthAdam 2014-08-02 06:47:24

En Python 3, vous pouvez mettre le code suivant dans votre scripts.__init__.py fichier:

import os
import os.path as op

__all__ = [
    op.splitext(f)[0]  # remove .py extension
    for f in os.listdir(BASE_DIR)  # list contents of current dir
    if not f.startswith('_') and
    ((op.isfile(op.join(BASE_DIR, f)) and f.endswith('.py')) or
     (op.isdir(op.join(BASE_DIR, f)) and op.isfile(op.join(BASE_DIR, f, '__init__.py'))))
]

from . import *  # to make `scripts.script1` work after `import script`

Pour plus d'informations sur les importations de Python, je recommande le discours de David Beazley à PyCon 2015: https://youtu.be/0oTh1CXRaQ0

0
répondu ostrokach 2016-05-08 20:06:24