Как я могу использовать 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) эту функцию, чтобы заставить ее работать с методами экземпляра?
Ответы
Ответ 1
Update: As of Python 3.8, functools.singledispatchmethod
allows single dispatch on methods, classmethods, abstractmethods,
и staticmethods.
Для более старых версий Python см. остальную часть этого ответа.
Глядя на источник для 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]
Ответ 2
Декоратор - это, по сути, оболочка, которая принимает упакованную функцию в качестве аргумента и возвращает другую функцию.
Как указано в принятом ответе, 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
-compatible он предоставляет декоратор 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)
Ответ 3
https://decorator.readthedocs.io/en/latest/tests.documentation.html#multiple-dispatch
также предоставить хорошее решение.