Программный импорт в Python 3?

#python #python-3.x

#python #python-3.x

Вопрос:

Допустим, у меня есть каталог foo , содержащий некоторые .py файлы.

Каждый из этих файлов python имеет определенный класс Bar .

Скажем, было вызвано три .py файла baz.py , qux.py и quux.py — затем, чтобы получить классы bar, которые я мог бы написать:

 import foo.baz
import foo.qux
import foo.quux

bars = [
    foo.baz.Bar,
    foo.qux.Bar,
    foo.quux.Bar,
]
 

Однако, вместо того, чтобы перечислять весь подобный импорт и все Bar имена, я хочу заполнить bars его программно.

То есть я хочу написать некоторый код для заполнения bars , который не изменяется при .py добавлении или удалении файлов из foo каталога.

Одним из способов сделать это может быть использование Path('foo').iterdir() для перечисления .py файлов, но тогда как мне выполнить программирование import строки и назвать ее Bar класс?

Или есть какой-то совершенно другой подход для достижения этой цели?

 bars = []
for py_file in the directory foo:
   import foo.py_file  ???
   bars.append(foo.py_file.Bar)  ???
 

Комментарии:

1. Взгляните на встроенный пакет importlib

Ответ №1:

Вот один из подходов, использующих glob module для сопоставления имен путей модулей, которые мы хотели бы импортировать, и importlib ‘s SourceFileLoader для импорта.

 from glob import glob
from importlib.machinery import SourceFileLoader

modules = glob('foo/*.py')

bars = list(map(lambda pathname: SourceFileLoader(".", pathname).load_module().Bar, modules))
 

Комментарии:

1. Я не понимаю, что SourceFileLoader(".", ...) это значит. Что значит передавать "." в качестве fullname параметра SourceFileLoader ? Будут ли какие-либо инструкции import, содержащиеся в загруженных файлах, продолжать работать правильно?

Ответ №2:

Вы можете использовать importlib для динамического импорта модулей. Но сначала вам нужно их найти. Если вы включите foo.__init__.py в исходный код, то тогда foo.__file__ будет это имя. Вы можете использовать это как основу вашего глобуса, но вам нужно убедиться, что вы не пытаетесь импортировать __init__.py себя. Этот метод хорош, потому что вам не нужно беспокоиться о том, что другой бит источника импортирует то же имя модуля, но получает другой модуль в памяти.

 from pathlib import Path
import importlib
import foo

def no_magic(paths):
    for path in paths:
        if not path.name.startswith("_"):
            yield path

bars = []
for py in no_magic(Path(foo.__file__).resolve().parent.glob("*.py")):
    mod = importlib.import_module(f"foo.{py.stem}", foo)
    bars.append(mod.Bar)

print(bars)