ラグジュアリー ロード モジュール システム
ErisPulse SDK は、モジュールを実際に必要になるまで初期化しない強力なラグジュアリー ロード モジュール システムを提供し、アプリケーションの起動速度とメモリ効率を大幅に向上させます。
概要
ErisPulse のコア機能の 1 つである遅延ロードモジュールシステムは、以下の方法で動作します。
- 遅延初期化:モジュールは、初めてアクセスされたときにのみ実際に読み込まれ、初期化されます。
- 透明な使用:開発者にとって、遅延ロードモジュールは通常のモジュールと使用上ほとんど違いがありません。
- 自動依存管理:モジュールの依存関係は、使用されるときに自動的に初期化されます。
- ライフサイクルサポート:
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 |
必須 | コマンド名;on_load での @command(name) と一致する必要がある。一致しないと、起動後にプレースホルダーが削除され、コマンドが存在しなくなる。 |
help |
str |
回帰チェーン | Help に表示される説明;宣言していない場合は回帰チェーンから値を取得(以下参照) |
usage |
str |
自動生成 | 用法行、デフォルトは {prefix}{name} |
group |
str |
None |
コマンドグループ |
aliases |
list[str] |
[] |
別名として同時に登録。別名の入力でもトリガーとして機能する |
hidden |
bool |
False |
True の場合、プレースホルダーコマンドも非表示(起動後の実際のコマンドの非表示の意味と一致);コマンド名を知っているユーザーの入力でもトリガーとして機能する |
サポートしていない priority / permission / master:プレースホルダーコマンドの使命はトリガーの起動のみであり、権限チェックは起動後の実際のコマンドが実行する(プレースホルダー段階で権限をブロックすると、「コマンド入力で起動」が無効になる)。
プレースホルダーコマンドの help 回帰チェーン
モジュールがロードされていない状態で Help に表示されるコマンドの説明は、以下の順序で値を取得します(最初に取得した値が使用されます):
- dict 形式で宣言されたコマンドレベルの
help(最も正確) - モジュールの
get_meta()のdescription - モジュールの
__description__属性 - パッケージのメタデータの
Summary(PyPI パッケージの概要) - 一般的なメッセージ:「このコマンドは遅延ロードモジュール X から来ています。初めて使用すると、モジュールが自動的にロードされます」
トリガーの意味
- イベント stub:対応するイベントマネージャーに非常に低い優先度(
ACTIVATION_STUB_PRIORITY)で登録され、通常のハンドラの後に実行されます。起動後、現在のイベントをモジュールの実際のハンドラに転送します。 - コマンド stub:プレースホルダーコマンドを登録します。起動後、プレースホルダーは削除され、実際のコマンドがそのトリガーを引き継ぎます。
- 再入防止:
asyncio.Lockを使用して、並行トリガー下で一度だけ起動されるように保証します。 - スコープフィルタリング:stub にはモジュールの所有者アイデンティティが含まれており、モジュールが Bot / セッション / プラットフォームに対して有効化されていない場合はトリガーされません。
- 失敗時の意味:起動に失敗した場合、再試行せず、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()で検索する場合でも、
「登録済みだがまだロードされていない」ラグロードモジュールに対しては、すべて同じラグロードプロキシが返り、
そのプロパティにアクセスすることで初めてモジュールの初期化がトリガーされます:
# 3つの方法で取得できるのはすべてラグロードプロキシです(モジュールがロードされていない場合)、動作は一貫しており、ユーザーには透明です
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を放棄せずに済む。
懒惰ロードを禁止することを推奨する場面(lazy_load=False)
- 起動時に即座に準備が必要なモジュール(他のモジュールに基礎サービスを提供するコアモジュールなど)
- 高頻度でトリガーされるリスナー(各メッセージごとに処理が必要) —
activate_onによる転送には1回のアクティベートオーバーヘッドがあるため、高頻度の場面では即時ロードした方が直接的 - タイマータスクモジュール
- アプリケーション起動時に初期化が必要なモジュール
priorityパラメータは、即時ロードされるモジュール間の初期化順序を制御します。数値が大きいほど先に初期化されます。同じ優先度のモジュールは登録順にロードされます。
注意事項
- モジュールが遅延ロードを使用している場合、ErisPulse内で他のモジュールによって一度も呼び出されない場合、そのモジュールは決して初期化されません。
- モジュール内にEventの監視など、他のモジュールを積極的に監視するモジュールが含まれている場合、2つの選択肢があります:
activate_onトリガーを宣言して遅延ロードを維持し、イベントが到達したときに自動的にアクティブ化するか、または即時ロードを必要とすることを宣言する(lazy_load=False)か、さもなければモジュールの正常な業務に影響を及ぼす可能性があります。 - 特殊な要望がない限り、遅延ロードを無効にすることは推奨しません。そうしないと、依存管理やライフサイクルイベントなどの問題が生じる可能性があります。
activate_onのコマンド dict 声明において、nameはモジュールのon_loadで@command()によって登録された実際のコマンド名と一致する必要があります。一致しない場合、モジュールがアクティブ化された後にプレースホルダーコマンドが解除され、宣言と実装が一致しないコマンドは存在しません。
関連ドキュメント
- モジュール開発ガイド - モジュール開発の方法を学ぶ
- ベストプラクティス - さらにベストプラクティスについて学ぶ