Метод декорирования (перегрузка методов класса) - PullRequest
4 голосов
/ 15 мая 2011

Вдохновленный ответом Мухаммеда Алкарури в Что хорошо используется для «аннотаций функций» 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, так как это функция .

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

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

1 Ответ

5 голосов
/ 15 мая 2011

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

Короче говоря, когда вы делаете 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, который выполняет эту привязку.

...