目标:
我希望能够通过“直接调用”动态导入子包中的所有功能
用法:
我的项目:
project/
|-- main.py
|-- src/
|---- __init__.py
|---- foo.py
|---- bar.py
foo.py
仅具有一项功能:
def foo_funct():
print("foo")
bar.py
仅具有一项功能:
def bar_funct():
print("bar")
最后是main.py
:
from src import *
(...)
foo_funct()
bar_funct()
(...)
评论:
如果我的__init__.py
是这样的
import os
__all__ = [i.replace(".py", "") for i in os.listdir(os.getcwd()+"/src/") if "__" not in i]
我将能够呼叫foo.foo_funct()
或bar.bar_funct()
,但不能呼叫foo_funct()
或bar_funct()
如果我的__init__.py
是这样的:
from src.foo import *
from src.bar import *
我将能够调用foo_funct()
或bar_funct()
,但是对于每个新的子包,我都必须修改我的__init__.py
假设from src import *
并不是最pythonic的方法,并假设由于诸如a.tree_funct()
和b.tree_funct()
之类的命名冲突而进行直接调用可能非常危险,有什么方法可以达到我的目标?
答案 0 :(得分:0)
我个人比较喜欢保持明确,只将包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)