extension method(拡張メソッド)とは
拡張メソッドを使用すると、新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく既存の型にメソッドを "追加" できます。
実装してみる
C#のような拡張メソッドの実現は困難なので、ここでは拡張用クラスで対象型のインスタンスをラップして、オブジェクト指向的なメソッド実行が可能、もしくは、単なる関数として実行可能であることを目指します(継承ではなく、移譲によるプログラミング)。
公式ドキュメントによると、__get__
を実装することで、関数として振る舞うか、メソッドとして振る舞うか制御可能なので、ここに細工を入れます。
次のコードは、メソッドに渡されるself
をself.__root__
に置換し、擬似的なextension methodを実現します。
from functools import wraps
from types import MethodType
class ExtensionMethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.__func__ # インスタンスなし時はそのまま関数を返す
func = self.__func__
@wraps(func) # 関数の引数情報などを引き継ぎ
def wrapper(self, *args, **kwargs):
return func(getattr(self, "__root__"), *args, **kwargs) # selfをself.__root__に置換
return MethodType(wrapper, obj) # インスタンスと関数をバインドしたメソッドを返す
class MyExtension:
def __init__(self, __root__):
self.__root__ = __root__
@ExtensionMethod
def add_one(self):
return self + 1
print(MyExtension.add_one(0)) # => 1
obj = MyExtension(10)
print(obj.add_one()) # => 11
こんな感じでしょうか。
あんまり嬉しくないな(オイ
単純に継承した方が多くの場合で話が早いのですが、__root__
のインターフェースに対してプログラムするので、継承せずに様々な型をオブジェクト的に扱えるのが利点ではあります。
mypyやpydanticのような型チェッカーと組み合わせることで、活路があるかもしれませんね。
注意
ExtensionMethod
に対してデコレータを付与した場合、ExtensionMethod
インスタンスに対してデコレータが動作します。
class MyExtension:
def __init__(self, __root__):
self.__root__ = __root__
@your_decorator # こういうの
@ExtensionMethod
def add_one(self):
return self + 1
クラスからメソッドへのアクセス時は、__get__
が動作して、関数かメソッドを返しますが、クラス生成中に直接アクセスしてはいけないということです。
関数として扱わせるには、__call__
を実装したり、もうひと手間必要になります。(ここでは、コードが冗長になるので紹介しません。)
また、mypyなどの静的型チェッカーを利用すると、selfの型が自身の型と一致しないので、エラーを出しまくります。
@dataclass
class MyExtension:
__root__: str
@ExtensionMethod
def add_one(self: str): # mypyはselfをMyExtensionを想定するのでエラーとなる
return self + 1
アイデア
MyExtensionへの属性アクセス時に、ExtensionMethod以外の属性を__root__
の属性にマップすれば、より拡張メソッドに近い振る舞いを実装できるかも。
まとめ
あまり実用的じゃない。