-From Extension Method (C # Programming Guide)
Extension methods allow you to "add" a method to an existing type without creating a new derived type, recompiling, or modifying the original type.
Since it is difficult to realize an extension method like C #, here we aim to wrap an instance of the target type in an extension class so that object-oriented method execution is possible, or it can be executed as a simple function ( Programming by transfer, not inheritance).
According to the Official Document, by implementing __get__, it is possible to control whether it behaves as a function or a method, so I will put some work here.
The following code replaces the self passed to the method with self .__ root __ to implement a pseudo-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__  #If there is no instance, the function is returned as it is
        func = self.__func__
        @wraps(func)  #Inherit function argument information, etc.
        def wrapper(self, *args, **kwargs):
            return func(getattr(self, "__root__"), *args, **kwargs)  #self to self.__root__Replace with
        return MethodType(wrapper, obj)  #Returns a method that binds an instance and a function
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
Is it like this? I'm not so happy (Oy
In many cases, simply inheriting is quicker, but since it is programmed for the __root__ interface, it has the advantage of being able to handle various types as objects without inheriting.
There may be a way to go by combining it with type checkers like mypy and pydantic.
If you give a decorator to the ExtensionMethod, the decorator works for the ExtensionMethod instance.
class MyExtension:
  def __init__(self, __root__):
    self.__root__ = __root__
  @your_decorator  #Like this
  @ExtensionMethod
  def add_one(self):
    return self + 1
When accessing a method from a class, __get__ works and returns a function or method, but it should not be accessed directly during class generation.
To treat it as a function, you need to implement __call__ or take another step. (I won't introduce it here because it makes the code redundant.)
Also, if you use a static type checker such as mypy, you will get an error because the type of self does not match your own type.
@dataclass
class MyExtension:
  __root__: str
  @ExtensionMethod
  def add_one(self: str):  #Since mypy assumes MyExtension for self, an error will occur.
    return self + 1
When accessing the attributes of MyExtension, if you map the attributes other than ExtensionMethod to the attributes of __root__, you may be able to implement the behavior closer to the extension method.
Not very practical.
Recommended Posts