динамический импорт "from src import *" для всех функций в пакете или подпакете - PullRequest
0 голосов
/ 05 февраля 2019

Цель:
Я хочу иметь возможность динамически импортировать все функции в подпакете с помощью «прямого вызова»

Использование:
мой проект:

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() или bar.bar_funct(), но не foo_funct() или bar_funct()

  2. , если мой __init__.py будет выглядеть примерно так:

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

    Я смогу позвонить foo_funct() или bar_funct(), но для каждого нового субпакета мне придется изменить свой __init__.py

  3. Предполагая, что from src import *не самый питонический метод, и если предположить, что прямые звонки могут быть очень опасными из-за возможных конфликтов имен, таких как a.tree_funct() и b.tree_funct(), есть ли какой-нибудь метод для достижения моей цели?

1 Ответ

0 голосов
/ 05 февраля 2019

Лично я предпочитаю держать вещи в явном виде и просто импортировать имена, которые являются частью API пакета, в __init__ явным образом.Ваш проект не изменится так быстро, что динамический импорт всего в __init__.py будет экономить время.

Но если вы хотите сделать это, то у вас есть несколько вариантов здесь.Если вам нужно поддерживать версии Python старше 3.7, вы можете обновить пространство имен пакета, щелкнув по globals() dictionary .Перечислите все .py файлы и импортируйте их, используя importlib.import_module() (или с __import__(), если вам нужно поддерживать версии Python до 2.7):

__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 *Функции 1024 * и __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)
...