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.
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é.
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__)
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()
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
Où 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.
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:
- incluez le paquet automodinit dans vos dépendances
setup.py
. - 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.
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
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...
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
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