首页 > 解决方案 > "from src import *" 动态导入包或子包中的所有函数

问题描述

目标:
我希望能够通过“直接调用”动态导入子包中的所有函数

用法:
我的项目:

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()
(...)

注释:

  1. 如果我__init__.py是这样的

    import os 
    __all__ = [i.replace(".py", "") for i in os.listdir(os.getcwd()+"/src/") if "__" not in i]
    

    我可以打电话foo.foo_funct()orbar.bar_funct()但不能foo_funct()orbar_funct()

  2. 如果我__init__.py是这样的:

    from src.foo import *
    from src.bar import *
    

    我可以打电话foo_funct(),或者bar_funct()但对于每个新的子包,我都必须修改我的__init__.py

  3. 假设这from src import *不是最pythonic的方法,并假设由于可能的命名冲突而直接调用可能非常危险,例如a.tree_funct()and b.tree_funct(),有什么方法可以达到我的目标吗?

标签: pythonpython-3.ximportpython-importimporterror

解决方案


就个人而言,我更喜欢保持明确,只是将作为包 API 一部分的名称显式导入__init__。您的项目不会变化得如此之快,以至于将所有内容动态导入到项目中将__init__.py节省时间。

但如果你想这样做,那么你有几个选择。如果您需要支持 3.7 之前的 Python 版本,则可以通过查看globals()字典来更新包命名空间。列出所有文件并使用(或者如果您需要支持 2.7 之前的 Python 版本).py导入它们:importlib.import_module()__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 及以上版本,则可以定义模块级__getattr__()__dir__()函数来实现动态查找。

在你的包文件中使用这些钩子__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)

推荐阅读