Как я могу использовать functools.singledispatch с методами экземпляра?
В Python 3.4 добавлена возможность определять перегрузку функций статическими методами. По сути, это пример из документации:
from functools import singledispatch
class TestClass(object):
@singledispatch
def test_method(arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@test_method.register(int)
def _(arg):
print("Strength in numbers, eh?", end=" ")
print(arg)
@test_method.register(list)
def _(arg):
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
if __name__ == '__main__':
TestClass.test_method(55555)
TestClass.test_method([33, 22, 11])
В чистом виде singledispatch
Реализация полагается на первый аргумент для идентификации типа, поэтому сложно распространить эту функциональность на методы экземпляра.
У кого-нибудь есть совет, как использовать (или jerry-rig) эту функциональность, чтобы заставить ее работать с методами экземпляра?
3 ответа
Глядя на источник для singledispatch
мы видим, что декоратор возвращает функцию wrapper()
, который выбирает функцию для вызова из зарегистрированных на основе типа args[0]
...
def wrapper(*args, **kw):
return dispatch(args[0].__class__)(*args, **kw)
... что хорошо для обычной функции, но не очень полезно для метода экземпляра, первый аргумент которого всегда будет self
,
Мы можем, однако, написать новый декоратор methdispatch
, который опирается на singledispatch
сделать тяжелую работу, но вместо этого возвращает функцию-оболочку, которая выбирает, какую зарегистрированную функцию вызывать в зависимости от типа args[1]
:
from functools import singledispatch, update_wrapper
def methdispatch(func):
dispatcher = singledispatch(func)
def wrapper(*args, **kw):
return dispatcher.dispatch(args[1].__class__)(*args, **kw)
wrapper.register = dispatcher.register
update_wrapper(wrapper, func)
return wrapper
Вот простой пример использования декоратора:
class Patchwork(object):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
@methdispatch
def get(self, arg):
return getattr(self, arg, None)
@get.register(list)
def _(self, arg):
return [self.get(x) for x in arg]
Обратите внимание, что оба украшены get()
метод и метод зарегистрирован list
иметь начальный self
аргумент как обычно.
Тестирование Patchwork
учебный класс:
>>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a", "c"])
[1, 3]
Декоратор - это, по сути, оболочка, которая принимает упакованную функцию в качестве аргумента и возвращает другую функцию.
Как указано в принятом ответе, singledispatch
возвращает wrapper
который принимает первый аргумент как зарегистрированный тип - self
в экземплярах методов.
Как показано в этом ответе, в таких случаях вы можете написать другую обертку, чтобы обезьяна исправила декоратор. Но такие хакерские исправления не всегда являются лучшим вариантом.
Как и в случае с любой другой функцией, вы можете вызывать обертку и передавать ей аргументы в явном виде, что кажется мне проще, понятнее и удобнее для чтения, если такая перегрузка метода редко делается в пакете.
from functools import singledispatch
class TestClass(object):
def __init__(self):
self.test_method = singledispatch(self.test_method)
self.test_method.register(int, self._test_method_int)
self.test_method.register(list, self._test_method_list)
def test_method(self, arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
def _test_method_int(self, arg):
print("Strength in numbers, eh?", end=" ")
print(arg)
def _test_method_list(self, arg):
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
if __name__ == '__main__':
test = TestClass()
test.test_method(55555)
test.test_method([33, 22, 11])
Есть еще один модуль, multipledispatch
(не стандартный, но включенный в Anaconda и без каких-либо нестандартных зависимостей), что, как следует из названия, отличается singledispatch
Позволяет мультиметоды.
В дополнение к Dispatcher
объекты, с singledispatch
совместимый синтаксис, он обеспечивает dispatch
декоратор, который скрывает создание и манипулирование этими объектами от пользователя.
Декоратор диспетчеризации использует имя функции, чтобы выбрать соответствующий объект Dispatcher, к которому он добавляет новую сигнатуру / функцию. Когда он встречает новое имя функции, он создает новый объект Dispatcher и сохраняет пару имя / диспетчер в пространстве имен для использования в будущем.
Например:
from types import LambdaType
from multipledispatch import dispatch
class TestClass(object):
@dispatch(object)
def test_method(self, arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@dispatch(int, float)
def test_method(self, arg, arg2):
print("Strength in numbers, eh?", end=" ")
print(arg + arg2)
@dispatch((list, tuple), LambdaType, type)
def test_method(self, arg, arg2, arg3):
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, arg3(arg2(elem)))
if __name__ == '__main__':
test = TestClass()
test.test_method(55555, 9.5)
test.test_method([33, 22, 11], lambda x: x*2, float)
Functools Python 3.8 представили перегрузку функций для методов экземпляра с использованием нового декоратора.@singledispatchmethod
.
Согласно документам;
отправка происходит по типу первого не-я или не-cls аргумента.
Следовательноtype
для аргумента, который следует сразу послеself
это тот, который запускает отправку метода. Что-то вроде ниже.
from functools import singledispatchmethod
class Cooking:
@singledispatchmethod
def cook(self, arg):
return f"I'm cooking {arg} eggs."
@cook.register
def _(self, arg: int):
return f"I'm cooking {arg} eggs."
@cook.register
def _(self, arg: bool):
return f"Am I cooking eggs? {arg}"
f = Cooking()
print(f.cook('many'))
# I'm cooking many eggs.
print(f.cook(50))
# I'm cooking 50 eggs.
print(f.cook(True))
# Am I cooking eggs? True