web-dev-qa-db-fra.com

Comment importer tous les sous-modules?

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

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

A partir de main.py, le module scripts est importé. J'ai essayé d'utiliser pkgutils.walk_packages en combinaison avec __all__, mais en l'utilisant, je ne peux importer que tous les sous-modules directement sous main à l'aide de from scripts import *. Je voudrais tous les obtenir 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 sous-modules au moment de l'exécution sans les spécifier explicitement dans __init__.py. Je peux utiliser pkgutils.walk_packages pour obtenir les noms de sous-modules (à moins que quelqu'un ne sache qu'il existe un meilleur moyen), mais je ne suis pas sûr du moyen le plus propre d'utiliser ces noms (ou peut-être les importateurs que walk_packages renvoie?) Pour les importer.

30
linkmaster03

Edit: Voici un moyen d'importer récursivement tout au moment de 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 et récursivement les packages qui l'utilisent. 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 une meilleure solution, mais c'est de toute façon ce que je peux faire de mieux.

Réponse originale (Pour le contexte, ignorez-le. J'ai mal compris la question au début):

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

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

Vous pouvez même vous passer de la définition de __all__, mais certaines choses (pydoc, si rien d'autre) fonctionneront plus proprement si vous le définissez, même s'il ne s'agit que d'une liste de ce que vous avez importé.

31
Joe Kington

Ceci est basé sur la réponse fournie par kolypto , mais sa réponse n’effectue pas l’importation récursive de packages, alors que tel est le cas. Bien que cela ne soit pas requis par la question principale, je pense 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 d'une recherche sur le sujet.

Il s’agit d’une manière propre et agréable d’importer les modules du sous-paquet. Elle doit également être portable et utilise la bibliothèque 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
        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__)
23
Mr. B

Fonctionne simplement et permet l’importation relative à l’intérieur des 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__)
    }

Usage:

__all__ = import_submodules(__name__).keys()
12
kolypto

Je me suis fatigué de ce problème moi-même, alors j'ai écrit un paquetage appelé automodinit pour y remédier. Vous pouvez l'obtenir de http://pypi.python.org/pypi/automodinit/ . L'utilisation est comme ça:

  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 tout! À 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 content de courir à l'intérieur des archives Zip et est donc sûr pour Zip.

2
Niall Douglas

Pas aussi propre que je voudrais, mais aucune des méthodes de nettoyage n'a fonctionné pour moi. Cela permet d'obtenir 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 __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 __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 que ce soit désordonné, il devrait être portable. J'ai utilisé ce code pour plusieurs packages différents.

2
user2561747

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 de eval. Une légère modification est qu'au lieu d'utiliser directement les packages __path__ pour walk_packages, j'utilise le répertoire parent des packages, puis uniquement les modules commençant par __+ ".". Ceci 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 faisait que pkgutil itéreait sur le paquet test de la bibliothèque de python; De plus, utiliser __path__ ne recurse pas dans les sous-répertoires des paquets. Tous ces problèmes ont été observés avec jython et python2.5, le code ci-dessous n’a été testé que dans jython à ce jour.

Notez également que la question des OP ne parle que de l'importation de tous les modules à partir d'un paquet, ce code importe également tous les paquets de manière récursive. 

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, j'essaierai de rendre cette dynamique dynamique avec un hook __getattr__ sur le paquet, afin que les modules réels ne soient importés que lorsqu'ils sont accédés ...

1
l4mpi

J'écrivais une petite bibliothèque personnelle et ajoutais de nouveaux modules tout le temps. J'ai donc é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 en a servi le but pour moi et cela pourrait aussi ê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
0
physicsmichael

Cela fonctionne bien pour moi dans Python 3.3. Notez que cela ne fonctionne que pour les sous-modules qui se trouvent dans des fichiers du même répertoire que le __init__.py. Certains travaux peuvent toutefois être améliorés pour la prise en charge de sous-modules dans des répertoires.

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
SzieberthAdam

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

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 Python, je recommande l'exposé de David Beazley à la conférence PyCon 2015: https://youtu.be/0oTh1CXRaQ0

0
ostrokach