简体   繁体   English

包或子包中所有功能的“ from src import *”动态导入

[英]“from src import *” dynamic import for all functions in a package or subpackage

Goal: 目标:
I want to be able to dynamically import all functions in a sub-package with 'a direct call' 我希望能够通过“直接调用”在子包中动态导入所有功能

Usage: 用法:
my project: 我的项目:

project/
|-- main.py
|-- src/
|---- __init__.py
|---- foo.py
|---- bar.py 

foo.py has just one function: foo.py只有一个功能:

def foo_funct(): 
    print("foo")

bar.py has just one function: bar.py只有一个功能:

def bar_funct():
    print("bar")

and finally main.py : 最后是main.py

from src import * 
(...)
foo_funct()
bar_funct()
(...)

Comments: 评论:

  1. if my __init__.py is something like this 如果我的__init__.py是这样的

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

    I will be able to call foo.foo_funct() or bar.bar_funct() but not foo_funct() or bar_funct() 我将能够调用foo.foo_funct()bar.bar_funct()但不能foo_funct()bar_funct()

  2. if my __init__.py is something like this: 如果我的__init__.py是这样的:

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

    I will be able to call foo_funct() or bar_funct() but for each new sub package I will have to modify my __init__.py 我将能够调用foo_funct()bar_funct()但是对于每个新的子包,我都必须修改__init__.py

  3. Assuming that from src import * is not the most pythonic method, and assuming that it could be very dangerous to have direct calls due to possible naming conflicts such as a.tree_funct() and b.tree_funct() , is there any method to reach my goal? 假设from src import *并不是大多数pythonic方法,并且假设由于可能的命名冲突(例如a.tree_funct()b.tree_funct()而进行直接调用可能非常危险,是否有任何方法可以实现我的目标?

Personally, I prefer to keep things explicit, and just import names that are part of the package API into __init__ explicitly. 就个人而言,我更喜欢保持明确,只将包API一部分的名称明确地导入__init__ Your project won't change so rapidly that dynamically importing everything into the __init__.py is going to be a time saver. 您的项目变化不会太快,以至于将所有内容动态导入__init__.py将节省时间。

But if you want to do this, then you have a few options here. 但是,如果您想这样做,那么这里有一些选择。 If you need to support Python versions older than 3.7, then you can update the package namespace by poking at the globals() dictionary . 如果需要支持3.7之前的Python版本,则可以通过在globals()字典中戳来更新包名称空间。 List all .py files and import them using importlib.import_module() (or with __import__() if you need to support Python versions before 2.7): 列出所有.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

The above keeps the namespace clean; 以上内容使命名空间保持整洁; after the _load_all_submodules() function has run it is removed from the package. _load_all_submodules()函数运行后,将其从软件包中删除。 It uses the __file__ global to determine the current path and finds any sibling .py files from there. 它使用__file__全局来确定当前路径,并从那里找到任何同级的.py文件。

If you only need to support Python 3.7 and up, you can define module-level __getattr__() and __dir__() functions to implement dynamic lookups. 如果只需要支持Python 3.7及更高版本,则可以定义模块级__getattr__()__dir__()函数来实现动态查找。

Using those hooks in your package __init__.py file could look like: 在包__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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM