tipfy (App-Engine 固有のマイクロ フレームワーク) には遅延読み込みがありますが、コードが処理する Web 要求である特定の「イベント」に対してのみです。他の Web フレームワークにもありますが、tipfy は小さくてシンプルなので、目的のためにそのソースを簡単に調べて模倣することができます。
そのため、「遅延読み込み」の問題が原因で、自分の好みに合ったより豊富なイベント フレームワークが見つからない場合は、呼び出し可能なオブジェクトの登録/サブスクリプションが必要なフレームワークを選択し、文字列の名前付け関数も登録できるようにすることができます。 tipfy と同じように。このように名前が付けられた関数は、もちろん、何らかのイベントを提供する必要がある場合にちょうど間に合うようにロードされます。
いくつかの単純化された架空のコードで例を挙げましょう。次のようなものを含むイベント フレームワークがあるとします。
import collections
servers = collections.defaultdict(list)
def register(eventname, callable):
servers[eventname].append(callable)
def raise(eventname, *a, **k):
for s in servers.get(eventname, ()):
s(*a, **k)
もちろん、実際のイベント フレームワークの内部はよりリッチになりますが、このようなものはその最下層で識別できます。
そのため、これには登録時に callable をロードする必要があります... それでも、フレームワークの内部に触れなくても、簡単に拡張できます。検討:
import sys
class LazyCall(object):
def __init__(self, name):
self.name = name
self.f = None
def __call__(self, *a, **k):
if self.f is None:
modname, funname = self.name.rsplit('.', 1)
if modname not in sys.modules:
__import__(modname)
self.f = getattr(sys.modules[modname], funname)
self.f(*a, **k)
もちろん、より良いエラー処理 &c が必要になりますが、これがその要点です。関数を指定する文字列 (たとえば'package.module.func'
) を、それを遅延ロードする方法を知っているラッパー オブジェクトにラップします。ここregister(LazyCall('package.module.func'))
で、手つかずのフレームワークにそのようなラッパーを登録し、要求に応じて遅延ロードします。
ところで、このユースケースは、何人かの卑劣な愚か者が、存在しない、または存在すべきではないと主張する Python イディオムのかなり良い例として使用できます。オブジェクトは、独自のクラスを動的に変更します。このイディオムの使用例は、2 つの状態のいずれかに存在するオブジェクトの「仲介者をカット」することです。最初の状態から 2 番目の状態への遷移は元に戻せません。ここで、怠惰な呼び出し元の最初の状態は「関数の名前は知っていますが、オブジェクトを持っていません」であり、2 番目の状態は「関数オブジェクトは知っています」です。最初から 2 番目への移動は元に戻せないため、必要に応じて毎回テストの小さなオーバーヘッド (またはStrategy
デザイン パターンの間接的なオーバーヘッド) を削減できます。
class _JustCallIt(object):
def __call__(self, *a, **k):
self.f(*a, **k)
class LazyCall(object):
def __init__(self, name):
self.name = name
self.f = None
def __call__(self, *a, **k):
modname, funname = self.name.rsplit('.', 1)
if modname not in sys.modules:
__import__(modname)
self.f = getattr(sys.modules[modname], funname)
self.__class__ = _JustCallIt
self.f(*a, **k)
if self.f is None:
基本的に各呼び出しから1 つのチェックを削減するだけなので、ここでのゲインは控えめです。しかし、それは本当の利点であり、前に名前を挙げた卑劣な愚か者を典型的な怒りと無知な狂乱に陥らせることを除いて、本当の欠点はありません(それを欠点と見なす場合).
とにかく、実装の選択は私ではなくあなた次第です-または、幸運なことに、彼らに;-)。
設計上の選択肢の 1 つとしてregister
、基本的には文字列引数を直接受け入れる (必要に応じてラップする) ようにパッチを適用するかtipfy
、登録サイトで明示的にラップして、元の状態のままにするかregister
(またはsubscribe
呼び出し方法を変更するか) を選択します。この特定のケースでは、「明示的は暗黙的よりも優れている」というマントラによってあまり重みを設定しません。
register(somevent, 'package.module.function')
と同じくらい明確です
register(somevent, LazyCall('package.module.function'))
つまり、何が起こっているのかが非常に明確であり、間違いなくよりクリーンで読みやすいものになっています。
それにもかかわらず、明示的なラッピング アプローチが基礎となるフレームワークに影響を与えないことは本当に素晴らしいことです。関数を渡すことができる場所ならどこでも、その関数の名前を (パッケージ、モジュール、および関数自体に名前を付ける文字列として) シームレスに渡すことができます。したがって、既存のフレームワークを改造する場合は、明示的なアプローチを採用します。
最後に、関数ではなく、(たとえば) 特定のクラスのインスタンス、またはそのようなインスタンスのバインドされたメソッドである callable を登録する場合は、目的のために &cLazyCall
などのバリアントに拡張できます。LazyInstantiateAndCall
もちろん、アーキテクチャは少し複雑になりますが (たとえば、新しいオブジェクトをインスタンス化する方法や既存のオブジェクトを識別する方法が必要なため)、その作業を適切に設計されたファクトリ システムに委譲することで、複雑になりすぎないようにする必要があります。悪い。ただし、この回答はすでにかなり長いため、そのような改良についてこれ以上深く掘り下げることはしません。とにかく、多くの場合、単純な「関数に名前を付ける」アプローチで十分です!-)