Метод декорирования (перегрузка методов класса)

Вдохновленный ответом Мухаммеда Алькарури в Каковы преимущества аннотаций функций Python3, я хочу сделать это multimethod для методов, а не для обычных функций. Однако когда я это сделаю

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
    if function is None:
        raise TypeError("no match")
    return function(*args)
def register(self, types, function):
    if types in self.typemap:
        raise TypeError("duplicate registration")
    self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)
    types = tuple(function.__annotations__.values())
    mm.register(types, function)
    return mm

class A:
@multimethod
def foo(self, a: int):
    return "an int"

a = A() 
print( a.foo( 1 ) ) 

Получил вот что:

Traceback (most recent call last):
  File "test.py", line 33, in <module>
    print( a.foo( 1 ) )
  File "test.py", line 12, in __call__
    return function(*args)
TypeError: foo() takes exactly 2 arguments (1 given)

Что кажется ожидаемым, как объясняется в Украшении метода, из-за аргумента self.

Но я понятия не имею, как заставить его работать. Ну, когда я удаляю "себя", он работает (почти) нормально, но я не хочу его удалять. Обратите внимание, что я делаю это для практики, я знаю, что есть некоторые библиотеки, обеспечивающие перегрузку методов.

Что я пробовал:

  • очень глупо, но захотелось попробовать - добавил параметр self в def multimethod( function ) - та же ошибка

  • Я думал добавить в __init__ из class MultiMethod третий параметр - obj и сохранить self как член, но я не могу сделать это через multimethod, поскольку это функция.

  • Я не хочу добавлять параметры для декоратора, поэтому эти параметры (если это вообще возможно) игнорируются

Я прочитал несколько похожих вопросов, но не нашел того, что искал. Я почти уверен, что это фиктивный вопрос, но у меня закончились идеи.


person Kiril Kirov    schedule 15.05.2011    source источник


Ответы (1)


Основная проблема заключается в том, что вы используете класс вместо функции. Не существует механизма для привязки этого класса к экземпляру, из которого он вызывается, в отличие от функции, в которой это происходит автоматически.

Короче говоря, когда вы выполняете a.foo( .. ), он возвращает MultiMethod, но этот объект не знает, что он должен быть привязан к a.

Вы должны так или иначе пройти в инстанс. Один из простых способов - обернуть все это функцией и позволить Python сделать это:

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}

    # self = a MultiMethod instance, instance = the object we want to bind to
    def __call__(self, instance, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)

        if function is None:
            raise TypeError("no match")
        return function(instance, *args)

    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)

    types = tuple(function.__annotations__.values())
    mm.register(types, function)
    # return a function instead of a object - Python binds this automatically
    def getter(instance, *args, **kwargs):
        return mm(instance, *args, **kwargs)
    return getter

class A:
    @multimethod
    def foo(self, a: int):
        return "an int", a

a = A() 
print( a.foo( 1 ) )

Более сложным способом было бы написать свой собственный дескриптор в классе A, который выполняет эту привязку.

person Jochen Ritzel    schedule 15.05.2011