Система ленивой загрузки модулей
ErisPulse SDK предоставляет мощную систему ленивой загрузки модулей, которая позволяет инициализировать модули только тогда, когда они действительно необходимы, значительно повышая скорость запуска приложения и эффективность использования памяти.
Обзор
Система ленивого загрузки модулей является одной из ключевых особенностей ErisPulse. Она работает следующим образом:
- Отложенная инициализация: модуль загружается и инициализируется только при первом обращении
- Прозрачное использование: для разработчиков ленивые модули практически не отличаются от обычных модулей
- Автоматическое управление зависимостями: зависимости модуля автоматически инициализируются при использовании
- Поддержка жизненного цикла: для модулей, унаследованных от
BaseModule, автоматически вызываются методы жизненного цикла
Принцип работы
Класс LazyModule
Основой системы ленивой загрузки является класс LazyModule, который является обёрткой, фактически инициализирующей модуль только при первом обращении.
Процесс инициализации
При первом обращении к модулю LazyModule выполняет следующие действия:
- Получает информацию о параметрах
__init__класса модуля - Определяет, следует ли передавать ссылку на
sdk - Устанавливает атрибут
moduleInfoмодуля - Для модулей, унаследованных от
BaseModule, вызывает методon_load - Запускает событие жизненного цикла
module.init
Событийное ленивое активирование (activate_on)
Note
Эта функция требует ErisPulse 2.8.0+.
Модули с lazy_load=True по умолчанию загружаются только при первом обращении к атрибуту. Если модуль зарегистрировал обработчики команд или событий, традиционный подход предполагает полную загрузку (lazy_load=False). activate_on предоставляет третий вариант: объявить триггер, и при первом совпадающем событии/команде модуль будет автоматически активирован — он не будет постоянно в памяти, но при этом не упустит входной триггер.
from ErisPulse.loaders import ModuleLoadStrategy
class MyModule(BaseModule):
@staticmethod
def get_load_strategy():
return ModuleLoadStrategy(
lazy_load=True,
activate_on=[
# ---- События (пассивные, не требуют участия пользователя)----
"message", # Тип события: любое сообщение
{"notice": "group_member_increase"}, # Тип + detail_type
{"message": ["private", "group"]}, # Тип + несколько detail_type
# ---- Команды (активные, ввод пользователя, отображаются в Help)----
{"command": "roll"}, # Сокращённая форма: имя команды
{"command": ["roll", "dice"]}, # Список имён команд
{"command": { # Форма dict (обязательно name)
"name": "dice",
"help": "Бросить кубик",
"usage": "/dice",
"group": "Развлечения",
"aliases": ["d"],
"hidden": False,
}},
],
)
Параметры команды в формате dict
Формат dict отражает пользовательские параметры декоратора @command(), и используется для регистрации временной команды до загрузки модуля:
| Параметр | Тип | Значение по умолчанию | Описание |
|---|---|---|---|
name |
str |
Обязательно | Имя команды; должно совпадать с @command(name) в on_load, иначе временная команда будет удалена при активации, и команда не будет доступна |
help |
str |
Цепочка возврата | Описание команды в Help; если не указано, используется цепочка возврата (см. ниже) |
usage |
str |
Автоматически | Пример использования, по умолчанию {prefix}{name} |
group |
str |
None |
Группа команды |
aliases |
list[str] |
[] |
Список алиасов, также регистрируется, ввод алиаса также активирует модуль |
hidden |
bool |
False |
Если True, временная команда будет скрыта (семантика скрытости совпадает с активированной командой); пользователи, знающие имя команды, могут активировать её |
Не поддерживается priority / permission / master: временная команда предназначена только для активации, проверка прав осуществляется активированной командой (в момент активации проверка прав прерывает активацию).
Цепочка возврата для Help временной команды
Описание команды в Help, отображаемое до загрузки модуля, берётся по следующему порядку (первое найденное значение):
helpв команде dict (наиболее точное)descriptionизget_meta()__description__из модуляSummaryиз метаданных пакета (краткое описание пакета на PyPI)- Общее сообщение: «Эта команда из лениво загружаемого модуля X, при первом использовании модуль будет автоматически загружен»
Семантика триггеров
- Событийный stub: регистрируется с низким приоритетом (
ACTIVATION_STUB_PRIORITY) в соответствующий обработчик событий, и срабатывает в конце, после всех обычных обработчиков; при активации событие пересылается в настоящий обработчик модуля - Командный stub: регистрируется временная команда; при активации временная команда удаляется, настоящая команда принимает на себя текущее срабатывание
- Предотвращение повторного запуска:
asyncio.Lockгарантирует, что при одновременных срабатываниях модуль активируется только один раз - Фильтрация по области видимости: stub содержит идентификатор владельца модуля, модуль не активируется, если не включен для данного бота / сессии / платформы
- Семантика ошибки: при неудачной активации повторная попытка не предпринимается, stub также удаляется
- Удаление дубликатов: при смешанном объявлении сокращённых и dict-имён команд дубликаты удаляются (dict имеет приоритет); если dict не содержит
nameилиdetail_typeсобытия указан как dict, выводится предупреждение и игнорируется
Архитектурная диаграмма и полная семантика см. в Обзоре архитектуры.
Конфигурация ленивой загрузки
Глобальная конфигурация
В файле конфигурации включите/отключите глобальную ленивую загрузку:
[ErisPulse.framework]
enable_lazy_loading = true # true=включить ленивую загрузку (по умолчанию), false=отключить ленивую загрузку
Управление на уровне модуля
Модуль может контролировать стратегию загрузки, реализовав статический метод get_load_strategy():
from ErisPulse.Core.Bases import BaseModule
from ErisPulse.loaders import ModuleLoadStrategy
class MyModule(BaseModule):
@staticmethod
def get_load_strategy():
"""Возвращает стратегию загрузки модуля"""
return ModuleLoadStrategy(
lazy_load=False, # Возвращает False для немедленной загрузки
priority=100 # Приоритет загрузки, чем больше значение, тем выше приоритет
)
Использование модулей с ленивой загрузкой
Основное использование
Для разработчиков использование модулей с ленивой загрузкой практически не отличается от обычных модулей:
# Доступ к модулю с ленивой загрузкой через SDK
from ErisPulse import sdk
# Следующий вызов инициирует ленивую загрузку модуля
result = await sdk.my_module.my_method()
Единый вход для получения модуля
Независимо от того, получаете ли вы модуль через свойство SDK, свойство менеджера модулей или с помощью module.get(), для "зарегистрированных, но еще не загруженных" модулей с ленивой загрузкой будет возвращаться один и тот же прокси-объект ленивой загрузки. Инициализация происходит только при обращении к его свойствам:
# Все три способа возвращают один и тот же прокси-объект ленивой загрузки (когда модуль еще не загружен), поведение одинаково и для пользователя прозрачно
sdk.my_module # Точка входа для инициации загрузки
sdk.module.my_module # Также возвращает прокси-объект ленивой загрузки
sdk.module.get("my_module") # Также возвращает прокси-объект ленивой загрузки, сам вызов не инициирует загрузку
# Инициализация модуля происходит только при обращении к свойству прокси-объекта
result = await sdk.my_module.my_method()
module.get() — это интерфейс для запроса, сам вызов не инициирует загрузку:
- Если модуль уже загружен → возвращается реальный экземпляр
- Если модуль зарегистрирован, но не загружен → возвращается прокси-объект ленивой загрузки (инициализация происходит при обращении к свойству)
- Если модуль не зарегистрирован → возвращается
None
Если необходимо явно инициировать загрузку, используйте await sdk.load_module("my_module").
Асинхронная инициализация
Для модулей, требующих асинхронной инициализации, рекомендуется сначала явно загрузить модуль:
# Сначала явно загрузите модуль
await sdk.load_module("my_module")
# Затем используйте модуль
result = await sdk.my_module.my_method()
Синхронная инициализация
Для модулей, не требующих асинхронной инициализации, можно обращаться напрямую:
# Прямое обращение автоматически инициирует синхронную инициализацию
result = sdk.my_module.some_sync_method()
Рекомендуемые практики
При выборе стратегии загрузки можно использовать следующий процесс принятия решений:
flowchart TD
A["Объявление модуля<br/>get_load_strategy()"] --> B{"Требуется готовность при запуске<br/>или частое срабатывание?"}
B -->|"Да"| C["lazy_load=False<br/>Загрузка немедленно"]
B -->|"Нет"| D{"Регистрированы обработчики команд / событий?"}
D -->|"Да"| E["lazy_load=True + activate_on<br/>Активация при поступлении события / команды"]
D -->|"Нет"| F["lazy_load=True<br/>Загрузка при первом обращении к атрибуту"]
C --> G["Вызов on_load() при запуске"]
E --> H["Регистрация stub → инстанцирование при срабатывании"]
F --> I["Прокси LazyModule"]
Рекомендованные сценарии использования ленивой загрузки (lazy_load=True)
- Инструментальные классы, вызываемые пассивно (например, модуль запросов данных, преобразователи форматов и т.д., которые нужны только при вызове из других модулей)
- Модули, зарегистрировавшие обработчики команд / событий, но используемые нечасто — в сочетании с
activate_onможно объявить триггер, автоматически активирующий модуль при поступлении первого соответствующего события / команды, без отказа от ленивой загрузки
Рекомендованные сценарии отключения ленивой загрузки (lazy_load=False)
- Модули, требующие готовности при запуске (например, основные модули, предоставляющие базовые сервисы другим модулям)
- Часто вызываемые слушатели (каждое сообщение должно обрабатываться) —
activate_onимеет небольшую стоимость активации, поэтому для сценариев с высокой частотой вызовов прямая загрузка предпочтительнее - Модули с планировщиком задач
- Модули, требующие инициализации при запуске приложения
Параметр
priorityуправляет порядком инициализации модулей, загруженных немедленно: чем больше значение, тем раньше модуль будет инициализирован. Модули с одинаковым приоритетом загружаются в порядке их регистрации.
Примечания
- Если ваш модуль использует ленивую загрузку, и если другие модули никогда не вызывались внутри ErisPulse, ваш модуль никогда не будет инициализирован.
- Если ваш модуль содержит такие компоненты, как модули, слушающие события, или другие активно слушающие модули, у вас есть два варианта: объявить триггер
activate_on(сохранить ленивую загрузку, автоматически активировать при поступлении события) или объявить, что он должен быть загружен немедленно (lazy_load=False), иначе это повлияет на нормальное функционирование вашего модуля. - Мы не рекомендуем отключать ленивую загрузку, если только у вас нет особых потребностей, иначе это может привести к проблемам с зависимостями и событиями жизненного цикла.
- В командном словаре
activate_onобъявления,nameдолжен совпадать с реальным именем команды, зарегистрированной в модулеon_loadс помощью@command()— в противном случае, после активации модуля, команда-заполнитель будет отменена, и команда, объявленная не соответствующая реализации, не будет существовать.
Связанные документы
- Руководство по разработке модулей - изучите разработку модулей
- Лучшие практики - узнайте больше о лучших практиках