問題の歴史:
Pythonではすべてがオブジェクトであり、関数もオブジェクトです。クラスのインスタンスが関数のように振舞うことを可能にするメカニズムが実装されました。これを実現するために、オブジェクトを呼び出し可能にする特別な魔法のメソッド__call__が導入されました。
問題:
呼び出すときに動作を実行できるオブジェクト(例えば、状態を持つ関数、クロージャ、イベントハンドラのオブジェクトなど)を渡す必要がある場合があります。アーキテクチャの設計には、このような機能を正しく実装する方法を理解することが求められます。
解決策:
クラス内で__call__メソッドを実装することで、そのインスタンスを「関数のように」呼び出せるようにできます。これにより、クラスの特性(状態のカプセル化、継承、メソッド)と関数の特性(呼び出し可能性)を組み合わせることができます。このアプローチは、コマンドオブジェクト、複雑なハンドラ、ラッパーなどの作成に使用されます。
コード例:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # 15を出力
主な特徴:
__call__メソッドは通常の関数の属性(例えば、name__や__doc)を継承しますか?
いいえ、__call__メソッドを持つオブジェクトの__name__属性は存在しないか(クラスから取ることになります)メタデータは保存されません。
__call__を実装したオブジェクトは、本当の関数ですか?
いいえ、これはクラスのインスタンスであり、関数ではありません。単に「呼び出し可能」な振る舞いを実装しているだけです。例えば、isinstance(obj, types.FunctionType)で関数と比較するとFalseが返されます。
__call__を持つオブジェクトに、関数用のデコレーターを適用できますか?
通常、そのようなデコレーターは関数を期待しており、オブジェクトを期待していません(例えば、functools.lru_cache)。使用するとエラーが発生したり、全く機能しないことがあります。
利点:
ネガティブケース: プロジェクトで__call__を持つクラスを使ってロガーを実装し、設定(レベル、ファイル名)を保存しました。しかし、シグナルハンドラ関数は関数を期待していることを忘れており、ハンドラの登録時にエラーを受け取りました(object is not a function)。
利点: ロガーの柔軟な設定。 欠点: 期待されるインターフェースとの不整合。
ポジティブケース: 別のプロジェクトでは、パラメータを保存するために__call__を持つクラスを使用して複雑なデコレーターを作成し、テストを簡素化しました。
利点: 拡張性、使いやすさ。 欠点: 関数やラムダと比較してより多くのコードが必要。