Использование динамических импортов
В программировании импорты используются для подключения кода из других модулей, чтобы мы могли использовать их функциональность. Однако иногда наличие импортов в середине файла может сделать код немного запутанным и трудным для понимания. В этой части мы научимся использовать динамические импорты для решения этой проблемы. Динамические импорты - это мощная возможность, которая позволяет загружать модули во время выполнения программы, то есть мы загружаем модуль только тогда, когда он нам действительно нужен.
Сначала нам нужно удалить инструкции импорта, которые сейчас расположены после класса TableFormatter
. Эти импорты - статические, они загружаются при запуске программы. Для этого откройте файл tableformat/formatter.py
в WebIDE. После открытия файла найдите и удалите следующие строки:
from .formats.text import TextTableFormatter
from .formats.csv import CSVTableFormatter
from .formats.html import HTMLTableFormatter
Если вы попытаетесь запустить программу сейчас, выполнив следующую команду в терминале:
python3 stock.py
Программа не запустится. Причина в том, что форматеры не будут зарегистрированы в словаре _formats
. Вы увидите сообщение об ошибке о неизвестном формате. Это происходит потому, что программа не может найти классы форматеров, необходимые для корректной работы.
Чтобы исправить эту проблему, мы изменим функцию create_formatter
. Цель - динамически импортировать необходимый модуль, когда он понадобится. Обновите функцию, как показано ниже:
def create_formatter(name, column_formats=None, upper_headers=False):
if name not in TableFormatter._formats:
__import__(f'{__package__}.formats.{name}')
formatter_cls = TableFormatter._formats.get(name)
if not formatter_cls:
raise RuntimeError('Unknown format %s' % name)
if column_formats:
class formatter_cls(ColumnFormatMixin, formatter_cls):
formats = column_formats
if upper_headers:
class formatter_cls(UpperHeadersMixin, formatter_cls):
pass
return formatter_cls()
Самая важная строка в этой функции:
__import__(f'{__package__}.formats.{name}')
Эта строка динамически импортирует модуль на основе имени формата. Когда модуль импортируется, его подкласс TableFormatter
автоматически регистрируется. Это благодаря методу __init_subclass__
, который мы добавили ранее. Этот метод - специальный метод Python, который вызывается при создании подкласса, и в нашем случае он используется для регистрации класса форматера.
После внесения этих изменений сохраните файл. Затем запустите программу снова, используя следующую команду:
python3 stock.py
Теперь программа должна работать корректно, даже несмотря на то, что мы удалили статические импорты. Чтобы убедиться, что динамический импорт работает как ожидается, мы очистим словарь _formats
и вызовем функцию create_formatter
. Выполните следующую команду в терминале:
python3 -c "from structly.tableformat.formatter import TableFormatter, create_formatter; TableFormatter._formats.clear(); print('Before:', TableFormatter._formats); create_formatter('text'); print('After:', TableFormatter._formats)"
Вы должны увидеть вывод, похожий на следующий:
Before: {}
After: {'text': <class 'structly.tableformat.formats.text.TextTableFormatter'>}
Этот вывод подтверждает, что динамический импорт загружает модуль и регистрирует класс форматера при необходимости.
Используя динамические импорты и регистрацию классов, мы создали более чистую и поддерживаемую структуру кода. Вот преимущества:
- Теперь все импорты находятся в верхней части файла, что соответствует соглашениям Python. Это делает код легче читать и понимать.
- Мы устранили циклические импорты. Циклические импорты могут вызывать проблемы в программе, такие как бесконечные циклы или ошибки, трудно поддающиеся отладке.
- Код стал более гибким. Теперь мы можем добавлять новые форматеры без изменения функции
create_formatter
. Это очень полезно в реальной жизни, когда со временем могут добавляться новые функции.
Этот паттерн использования динамических импортов и регистрации классов широко применяется в системах плагинов и фреймворках. В этих системах компоненты должны загружаться динамически в зависимости от потребностей пользователя или требований программы.