Как импортировать все подмодули?
У меня есть структура каталогов следующим образом:
| main.py
| scripts
|--| __init__.py
| script1.py
| script2.py
| script3.py
Из main.py
импортируется модуль scripts
. Я попытался использовать pkgutils.walk_packages
в сочетании с __all__
, но, используя это, я могу импортировать все подмодули непосредственно под main
с помощью from scripts import *
. Я хотел бы получить их всех под scripts
. Какой будет самый чистый способ импортировать все подмодули scripts
, чтобы я мог получить доступ к scripts.script1
из main
?
EDIT: Мне жаль, что я немного расплывчата. Я хотел бы импортировать подмодули во время выполнения, не указав их явно в __init__.py
. Я могу использовать pkgutils.walk_packages
, чтобы получить имена подмодулей (если кто-то не знает лучшего способа), но я не уверен в самом чистом способе использования этих имен (или, может быть, ImpImporters, который walk_packages
возвращает?), Чтобы импортировать их.
Ответы
Ответ 1
Изменить: Здесь один из способов рекурсивно импортировать все во время выполнения...
Он использует exec, поэтому почти наверняка лучший способ, но он работает (даже для произвольно вложенных подпакетов, я думаю).
(Содержание __init__.py
в каталоге верхнего пакета)
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)
exec('%s = module' % module_name)
Я не использую __import__(__path__+'.'+module_name)
здесь, так как трудно правильно рекурсивно импортировать пакеты, используя это. Если у вас нет вложенных подпакетов и вы хотите избежать exec
/eval
, тем не менее, это один из способов сделать это.
Вероятно, лучший способ, но это лучшее, что я могу сделать, во всяком случае.
Оригинальный ответ (для контекста, игнорировать в противном случае. Я неправильно понял вопрос):
Как выглядит ваш scripts/__init__.py
? Это должно быть что-то вроде:
import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']
Вы можете даже обойтись без определения __all__
, но вещи (pydoc, если ничего больше) будут работать более чисто, если вы его определите, даже если это просто список того, что вы импортировали.
Ответ 2
Это основан на ответе, предоставленном kolypto, но его ответ не выполняет рекурсивный импорт пакетов, тогда как это делает. Хотя это не требуется по основному вопросу, я считаю, что рекурсивный импорт применяется и может быть очень полезным во многих подобных ситуациях. Я, например, нашел этот вопрос при поиске по теме.
Это хороший, чистый способ выполнения импорта модулей subpackage, а также должен быть переносимым, и он использует стандартную lib для 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
Применение:
# from main.py, as per the OP project structure
import scripts
import_submodules(scripts)
# Alternatively, from scripts.__init__.py
import_submodules(__name__)
Ответ 3
Просто работает и позволяет относительный импорт внутри пакетов:
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__)
}
Использование:
__all__ = import_submodules(__name__).keys()
Ответ 4
Я сам устал от этой проблемы, поэтому я написал пакет под названием automodinit для его исправления. Вы можете получить его из http://pypi.python.org/pypi/automodinit/. Использование выглядит так:
- Включите пакет automodinit в свои зависимости
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.
Что это! С этого момента при импорте модуля устанавливается __all__
на список файлов .py [co] в модуле и также импортирует каждый из этих файлов, как будто вы набрали:
for x in __all__: import x
Поэтому эффект from M import *
соответствует точно import M
.
automodinit счастлив работать из ZIP-архивов и поэтому является безопасным для ZIP.
Ответ 5
Не так чисто, как хотелось бы, но ни один из более чистых методов не работал у меня. Это позволяет выполнить указанное поведение:
Структура каталогов:
| pkg
|--| __init__.py
| main.py
| scripts
|--| __init__.py
| script1.py
| script2.py
| script3.py
Где pkg/scripts/__init__.py
пусто, а pkg/__init__.py
содержит:
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__)
Хотя это беспорядочно, оно должно быть портативным. Я использовал этот код для нескольких разных пакетов.
Ответ 6
Я писал небольшую личную библиотеку и добавлял новые модули все время, поэтому я написал оболочку script для поиска скриптов и создания __init__.py
. script выполняется только за пределами основного каталога моего пакета, pylux.
Я знаю, что это, вероятно, не тот ответ, который вы ищете, но он поставил перед собой цель, и он может быть полезен и для кого-то другого.
#!/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
Ответ 7
Я играл с Joe Kington Answer и создал решение, которое использует globals
и get/setattr
и, следовательно, не нуждается в eval. Небольшая модификация заключается в том, что вместо прямого использования пакетов __path__
для walk_packages
я использую родительский каталог пакетов, а затем импортирую только модули, начиная с __name__ + "."
. Это было сделано для надежного получения всех подпакетов из walk_packages
- в моем случае использования у меня был подпакет с именем test
, из-за которого pkgutil перебирал пакет test
из библиотеки python; кроме того, использование __path__
не будет возвращаться в подкаталоги пакетов. Все эти проблемы наблюдались с использованием jython и python2.5, код ниже проверяется только на jython.
Также обратите внимание, что вопрос OPs говорит только об импорте всех модулей из пакета, этот код рекурсивно импортирует все пакеты.
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)
В качестве будущего улучшения я попытаюсь сделать эту динамику с помощью __getattr__
hook на пакете, поэтому фактические модули будут импортироваться только тогда, когда они будут доступны...
Ответ 8
Это хорошо работает для меня в Python 3.3. Обратите внимание, что это работает только для подмодулей, которые находятся в файлах в том же каталоге, что и __init__.py
. Однако с некоторыми работами он может быть расширен для поддержки подмодулей в каталогах.
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
Ответ 9
В Python 3 вы можете поместить следующий код в свой файл 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`
Для получения дополнительной информации об импорте Python я рекомендую Дэвиду Безли поговорить на PyCon 2015: https://youtu.be/0oTh1CXRaQ0