如何导入所有子模块?
- 2025-03-12 08:55:00
- admin 原创
- 10
问题描述:
我有一个如下的目录结构:
| main.py
| scripts
|--| __init__.py
| script1.py
| script2.py
| script3.py
在 中main.py
,如果我import scripts
,这显然不允许我使用scripts.script1
。我知道我可以使用from scripts import *
来访问包中的模块scripts
,但是我只能直接将它们用作等scripts1
。scripts2
我该如何编写代码以便可以参考scripts.script1
里面的内容main.py
?
我尝试使用pkgutils.walk_packages
以及__all__
包的属性来获取子模块名称,但我无法找到使用这些字符串进行导入的方法。
解决方案 1:
编辑:这是在运行时递归导入所有内容的一种方法......
(顶级包目录中的内容__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)
globals()[module_name] = _module
我在这里不使用__import__(__path__+'.'+module_name)
,因为使用它很难正确地递归导入包。但是,如果您没有嵌套的子包,并且想避免使用globals()[module_name]
,那么这是一种方法。
可能有更好的方法,但无论如何,这是我能做的最好的。
原始答案(对于上下文,请忽略其他内容。我最初误解了这个问题):
你scripts/__init__.py
看起来怎么样?应该是这样的:
import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']
您甚至可以不定义__all__
,但是如果您定义它,事物(pydoc,如果没有别的)将会更干净地工作,即使它只是您导入内容的列表。
解决方案 2:
这是基于kolypto 提供的答案,但他的答案不执行包的递归导入,而这个则执行。虽然主要问题没有要求,但我相信递归导入适用并且在许多类似情况下非常有用。例如,我在搜索该主题时发现了这个问题。
这是一种很好的、干净的执行子包模块导入的方法,并且也应该是可移植的,它使用 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
try:
results[full_name] = importlib.import_module(full_name)
except ModuleNotFoundError:
continue
if recursive and is_pkg:
results.update(import_submodules(full_name))
return results
用法:
# from main.py, as per the OP's 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:
要加载包的所有子模块,可以使用这个简单的函数:
import importlib
import pkgutil
def import_submodules(module):
"""Import all submodules of a module, recursively."""
for loader, module_name, is_pkg in pkgutil.walk_packages(
module.__path__, module.__name__ + '.'):
importlib.import_module(module_name)
用例:加载 Flask 应用程序的所有数据库模型,以便 Flask-Migrate 可以检测到架构的变化。用法:
import myproject.models
import_submodules(myproject.models)
解决方案 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:
我自己也厌倦了这个问题,所以我写了一个名为 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 安全的。
解决方案 7:
为了确保子模块不会加载到不同于的位置module.__path__
,您可以使用这种方法:
def import_submodules(module, recursive=False):
"""Import all submodules of a module, recursively."""
from sys import modules
from pkgutil import walk_packages
from importlib.util import module_from_spec
module_stack = [walk_packages(
module.__path__,
module.__name__ + '.')
]
while module_stack:
gen = module_stack.pop()
for loader, module_name, is_pkg in gen:
_spec = loader.find_spec(module_name)
_module = module_from_spec(_spec)
_spec.loader.exec_module(_module)
modules[module_name] = _module
yield _module
if recursive:
module_stack.append(
walk_packages(
_module.__path__,
_module.__name__ + '.'
)
)
解决方案 8:
我正在编写一个小型个人库,并一直在添加新模块,因此我编写了一个 shell 脚本来查找脚本并创建__init__.py
。该脚本在我的包 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 ' -> ' 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 ' -> ' 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 " "$0 }'
done
解决方案 9:
我尝试了Joe Kington 的答案,并构建了一个使用globals
和的解决方案get/setattr
,因此不需要 eval。一个小小的修改是,不是直接使用包,__path__
而是walk_packages
使用包的父目录,然后只导入以开头的模块__name__ + "."
。这样做是为了可靠地从中获取所有子包walk_packages
- 在我的用例中,我有一个名为的子包,这导致 pkgutil从 python 的库test
中迭代包;此外,使用不会递归到包子目录中。所有这些问题都是使用 jython 和 python2.5 观察到的,到目前为止,下面的代码仅在 jython 中进行了测试。test
`__path__`
还要注意,OP 的问题只谈到从包中导入所有模块,此代码也递归导入所有包。
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__
包上的钩子使其动态化,因此实际的模块仅在被访问时才会被导入......
解决方案 10:
在 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