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.
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é.
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__)
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()
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:
setup.py
.__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.
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
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 __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.
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 ...
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
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
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