Динамическая загрузка модулей в Python и как она спасает при работе с pyspark
"Приобретение знаний - это как путешествие в неизведанные земли: чем больше вы исследуете, тем больше открытий вы делаете".
Библиотека importlib в Python предоставляет инструменты для динамической загрузки модулей. То есть она будет происходить не на этапе анализа кода интерпретатором, а во время выполнения программы. Это полезно, когда некоторые модули не известны до старта программы, например, как при работе с pyspark до инициализации переменных окружения с нужными путями.
Импорт модуля
Самый простой способ динамически загрузить модуль - использовать функцию import_module. Например, встроенный модуль math можно загрузить и использовать так:
import importlib import sys math_mod = importlib.import_module('math') math_mod.log(2)
Как указывал выше, этот способ спасет при работе с pyspark, вот как импортируются наиболее часто используемые модули:
F = importlib.import_module('pyspark.sql.functions') T = importlib.import_module('pyspark.sql.types')
Теперь рассмотрим пользовательский модуль следующего содержания:
def print_(s): print(s) a = 1 b = 2
Для его загрузки понадобится такой код:
func_mod = importlib.import_module('pkg.func') func_mod.a, func_mod.b func_mod.print_('hi')
Так можно извлечь нужные нам переменные по некоторому правилу:
[k for k,v in vars(func_mod).items() if isinstance(v, int)] vars(func_mod)['a']
Часто такое получение переменных применяется при тесте дагов Airflow (подробнее о vars можете прочитать тут):
from airflow import DAG [k for k,v in vars(func_mod).items() if isinstance(v, DAG)]
Перезагрузка модуля
Если в нашем пользовательском модуле инициализировать дополнительную переменную (c), то потребуется перезагрузка модуля:
def print_(s): print(s) a = 1 b = 2 c = 3
Иначе мы не сможем обратиться к переменной:
func_mod.c
ни даже после повторной загрузки модуля:
func_mod = importlib.import_module('pkg.func') func_mod.c
Это происходит из-за кеширования результатов загрузки модуля (подробнее смотри тут). Помочь может очистка специального словаря.
Так, для хранения информации о загруженных модулях используется словарь sys.modules. Когда модуль загружается, его имя добавляется в словарь. Это позволяет системе быстро определить, был ли модуль уже загружен или нет, чтобы избежать повторной загрузки и улучшить производительность.
Словарь sys.modules также может быть использован для управления процессом импорта, например, вы можете удалить оттуда запись о модуле, чтобы система его перезагрузила при очередном импорте:
sys.modules['pkg.func'] del sys.modules['pkg.func'] func_mod = importlib.import_module('pkg.func') func_mod.c
С функцией reload можно произвести перезагрузку модуля без манипуляций с sys.modules. Добавим еще одну переменную в наш модуль:
def print_(s): print(s) a = 1 b = 2 c = 3 d = 4
Как и ожидалось, сначала она не доступна:
func_mod.d
Однако после вызова reload все встает на свои места:
importlib.reload(func_mod) func_mod.d
Спецификация модуля
Спецификация модуля предоставляет больше возможностей для работы с модулями в Python и может быть полезна в различных сценариях, например, при изучении параметров модуля и принятия решения о его последующей загрузке.
Для получения объекта ModuleSpec, можно воспользоваться функцией find_spec модуля importlib.util:
spec = importlib.util.find_spec('pkg.func') # spec = importlib.util.spec_from_file_location('func', 'pkg/func.py') print(spec)
if spec is not None: print(f"Module name: {spec.name}") print(f"File location: {spec.origin}") print(f"Loader: {spec.loader}")
Так можно загрузить сам модуль по объекту спецификации:
func_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(func_mod)
Следует отметить, что этот способ не добавляет модуль в sys.modules:
sys.modules['pkg.func']
Однако ничто не мешает сделать это самим, если надо:
sys.modules['pkg.func'] = func_mod sys.modules['pkg.func']