就个人而言,我更喜欢保持明确,并且只是将作为包 API 一部分的名称显式导入到 __init__ 中。您的项目不会发生如此迅速的变化,以至于将所有内容动态导入__init__.py 会节省时间。
但是,如果您想这样做,那么您有几个选择。如果您需要支持早于 3.7 的 Python 版本,则可以通过戳globals() dictionary 来更新包命名空间。列出所有.py 文件并使用importlib.import_module() 导入它们(如果需要支持2.7 之前的Python 版本,则使用__import__()):
__all__ = []
def _load_all_submodules():
from pathlib import Path
from importlib import import_module
g = globals()
package_path = Path(__file__).resolve().parent
for pyfile in package_path.glob('*.py'):
module_name = pyfile.stem
if module_name == '__init__':
continue
module = import_module(f'.{module_name}', __package__)
names = getattr(
module, '__all__',
(n for n in dir(module) if n[:1] != '_'))
for name in names:
g[name] = getattr(module, name)
__all__.append(name)
_load_all_submodules()
del _load_all_submodules
以上内容保持命名空间干净; _load_all_submodules() 函数运行后,它会从包中删除。它使用__file__ 全局来确定当前路径并从那里找到任何同级.py 文件。
如果只需要支持 Python 3.7 及以上版本,可以定义module-level __getattr__() and __dir__() functions 实现动态查找。
在你的包 __init__.py 文件中使用这些钩子可能看起来像:
def _find_submodules():
from pathlib import Path
from importlib import import_module
package_path = Path(__file__).resolve().parent
return tuple(p.stem for p in package_path.glob('*.py') if p.stem != '__init__')
__submodules__ = _find_submodules()
del _find_submodules
def __dir__():
from importlib import import_module
names = []
for module_name in __submodules__:
module = import_module(f'.{module_name}', __package__)
try:
names += module.__all__
except AttributeError:
names += (n for n in dir(module) if n[:1] != '_')
return sorted(names)
__all__ = __dir__()
def __getattr__(name):
from importlib import import_module
for module_name in __submodules__:
module = import_module(f'.{module_name}', __package__)
try:
# cache the attribute so future imports don't call __getattr__ again
obj = getattr(module, name)
globals()[name] = obj
return obj
except AttributeError:
pass
raise AttributeError(name)