1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pythonでextension methodを実装してみる

Last updated at Posted at 2021-01-17

extension method(拡張メソッド)とは

拡張メソッドを使用すると、新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく既存の型にメソッドを "追加" できます。

実装してみる

C#のような拡張メソッドの実現は困難なので、ここでは拡張用クラスで対象型のインスタンスをラップして、オブジェクト指向的なメソッド実行が可能、もしくは、単なる関数として実行可能であることを目指します(継承ではなく、移譲によるプログラミング)。

公式ドキュメントによると、__get__を実装することで、関数として振る舞うか、メソッドとして振る舞うか制御可能なので、ここに細工を入れます。

次のコードは、メソッドに渡されるselfself.__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__の属性にマップすれば、より拡張メソッドに近い振る舞いを実装できるかも。

まとめ

あまり実用的じゃない。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?