Python Talk 2 - Python 3.12 Hot Reload
Python3.12 におけるホットアップデートの実装方法を記録する
ホットリロード
ホットリロード(Hot Reload)は、プログラムを再起動することなく更新できる技術と理解できます。この技術はゲーム業界で広く利用されており、開発者がゲームの問題を修正する際に、プレイヤーに影響を与えないように、静かな更新方法、つまりホットリロードを採用することがよくあります。
Pythonのホットリロード
Pythonは動的な言語であり、すべてがオブジェクトであり、ホットリロードが可能です。私たちは、Pythonにおいてホットリロードが必要なオブジェクトを大まかに二つに分けることができます:データと関数です。
データは、ゲーム内の数値や設定として考えることができます。たとえば、プレイヤーのレベル、装備などのいくつかのデータがあります。一部のデータはホットフィックスで更新すべきではない(たとえば、プレイヤーの現在のレベルや所持している装備などの変更はホットフィックスによって行うべきではありません)。一部のデータは更新したいと考えています(たとえば、装備の基本数値設定、スキルの基本数値設定、UI上のテキストなど)。
関数はゲームのロジックとして理解できます。これが基本的に私たちがホットアップデートしたいものです。ロジックのエラーは基本的にホットアップデート関数を通じて実現する必要があります。
Python3.12をホットアップデートする方法について具体的に見ていきましょう。
Hotfix
最初の方法はHotfixと呼ばれ、プログラム(クライアントプログラム/サーバープログラムのどちらでも)が特定のPythonコードを実行することで、データと関数の熱い更新を実現します。簡単なHotfixコードは次のようになります:
# hotfix code
# hotfix data
import weapon_data
weapon_data.gun.damage = 100
# hotfix func
import player
def new_fire_func(self, target):
target.health -= weapon_data.gun.damage
# ...
player.Player.fire_func = new_fire_func
以上のコードは、Hotfix の書き方を簡単に示しています。データ / 関数の変更後、プログラムがその後アクセスする際には新しいデータ / 関数を読み込んで実行します。
もしあなたが細かいことを気にするタイプであれば、次のような疑問が浮かぶかもしれません:他のコードがこれらの修正が必要なデータや関数を参照している場合、何が起こるのでしょうか?
# attack.py module
player_fire = player.Player.fire_func
def player_attack_by_gun(player, target):
player_fire(player, target)
# ...
解答は、前のHotfixではこの状況に対して効果がありません。fire_func
関数は他のモジュールにコピーされたものです。モジュール内で呼び出されるのは関数のコピーであり、本体を変更してもコピーには影響しません。
したがって、一般的なコードでは、モジュールレベルのデータ参照や関数参照をできるだけ減らす必要があります。このようなHotfixが機能しない状況を避けるためです。もしコードがすでにこのように書かれている場合、Hotfixはより多くの作業を必要とします。
データ/関数本体のHotfixを修正した後、引用された部分に対しても追加の修正を行います。これらの追加修正は見落とされやすいため、コード規範の観点から、できる限り複数の引用を避けることをお勧めします。
以上を踏まえると、ホットフィックスはアップデートの基本的なニーズを満たすことができますが、以下の問題が存在しています:
他がそれらのモジュールに明示的に参照されるデータ/関数が存在する場合、これらのモジュールへの参照を追加で修正する必要があります。Hotfix - 大量のデータや関数にホットフィックスが必要な場合、ホットフィックスのコードが非常に大きくなり、メンテナンスが難しくなり、ミスが起こりやすくなります。
Reload
本章のソースコードはここから入手できます:python_reloader
私たちがもっと望んでいるのは、自動ホット更新であり、追加でHotfixを書く必要はなく、コードファイルを更新するだけで、プログラムがReload関数を実行すれば自動的に新しい関数と新しいデータに置き換わるということです。この自動ホット更新の機能をReloadと呼んでいます。
Python3.12 で導入された importlib.reload 関数はモジュールを再読み込みする機能を提供していますが、全体的な再読み込みとして新しいモジュールオブジェクトを返すため、他のモジュールでの参照は自動的に変更されません。つまり、他のモジュールが reload されたモジュールを import した場合でも、アクセスするのは古いモジュールオブジェクトのままです。この機能は私たちの Hotfix とそれほど変わらないし、しかも全体的なモジュール再読み込みであり、どのデータを保持すべきかを制御できません。したがって、これらの要求を満たすために独自の Reload 機能を実装したいと考えています:
自動関数置換機能は、古い関数への参照を保持し、新しい関数の内容を実行します。 データを自動的に置換し、一部の置換を制御できます。 古いモジュールの参照を保持し、古いモジュールを介して新しいコンテンツにアクセスできるようにします。 - 需要 Reload のモジュールは制御可能です
これらの要件を満たすためには、Python の meta_path メカニズムを利用する必要があります。詳しい説明は公式ドキュメント the-meta-pathI'm sorry, but there is nothing to translate in the text you provided.
sys.meta_path内で、私たちのメタパスファインダーオブジェクトを定義できます。例えば、リロードに使用するファインダーをreload_finderと呼ぶことができます。このreload_finderは、find_spec関数を実装してspecオブジェクトを返す必要があります。Pythonはspecオブジェクトを取得すると、順番にspec.loader.create_moduleおよびspec.loader.exec_moduleを実行してモジュールのインポートを完了します。
もし新しいモジュールのコードを実行し、新しいモジュール内の関数と必要なデータを古いモジュールにコピーすることで、Reload の目的を達成できます。
上記の通り、find_spec
は最新のモジュールのソースコードをロードし、古いモジュールの __dict__
内で新しいモジュールのコードを実行します。その後、クラス/関数/データの参照と置換を処理するために ReloadModule
を呼び出します。MetaLoader
の目的は、meta_path メカニズムに適合し、Python仮想マシンに私たちが処理したモジュールオブジェクトを返すことです。
ロードされたプロセスを処理した後、ReloadModule
のおおよその実装を見てみましょう。
ReloadDict
の中では、異なるタイプのオブジェクトを区別して処理します。
- もしクラスであれば、
ReloadClass
を呼び出します。これにより、古いモジュールの参照が返され、クラスのメンバーが更新されます。 - 関数/メソッドの場合、
ReloadFunction
を呼び出すと、古いモジュールの参照が返され、関数の内部データが更新されます。 - もしデータであり、かつ保持する必要がある場合は、
new_dict[attr_name] = old_attr
にロールバックされます。 - 他のすべては新しい引用を保持してください。
- 存在しない関数を新しいモジュールから削除する
ReloadClass
とReloadFunction
の具体的なコードはここでは詳細に分析しませんが、興味があれば直接ソースコード。
Reload 全体の過程は、古いボトルに新しい酒を詰めると言える。モジュール/モジュールの関数/モジュールのクラス/モジュールのデータを有効に保つためには、これらのオブジェクトの参照(外部構造)を保持し、それらの内部データを更新する必要があります。たとえば、関数の場合、__code__
、__dict__
などのデータを更新し、関数が実行されると、新しいコードが実行されるようになります。
まとめ
本稿では、Python3の2つのホットリロード方法について詳しく説明します。それぞれに対応する適用シーンがあり、あなたにとって役立つことを願っています。何か疑問があれば、いつでもお気軽にご相談ください。
Original: https://wiki.disenone.site/ja
This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.
Visitors. Total Visits. Page Visits.
この投稿はChatGPTで翻訳されました。フィードバックはフィードバック中指出任何遗漏之处。