son - importar clases en python
¿Cómo puedo usar functools.singledispatch con métodos de instancia? (2)
Python 3.4 added la capacidad de definir la sobrecarga de funciones con métodos estáticos. Este es esencialmente el ejemplo de la documentación:
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])
En su forma más pura, la implementación de singledispatch
basa en el primer argumento para identificar el tipo, por lo que es complicado extender esta funcionalidad a los métodos de instancia.
¿Alguien tiene algún consejo sobre cómo usar (o jerry-rig) esta funcionalidad para que funcione con los métodos de instancia?
Al singledispatch
la source de singledispatch
, podemos ver que el decorador devuelve un wrapper()
función wrapper()
, que selecciona una función para llamar de las registradas según el tipo de args[0]
...
def wrapper(*args, **kw):
return dispatch(args[0].__class__)(*args, **kw)
... lo cual está bien para una función normal, pero no es muy útil para un método de instancia, cuyo primer argumento siempre será self
.
Sin embargo, podemos escribir un nuevo methdispatch
decorador, que se basa en un singledispatch
para hacer el trabajo pesado, pero en su lugar devuelve una función de envoltura que selecciona qué función registrada llamar según el tipo de 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
Aquí hay un ejemplo simple del decorador en uso:
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]
Observe que tanto el método get()
decorado como el método registrado en la list
tienen un autoargumento inicial como siempre.
Probando la clase Patchwork
:
>>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a", "c"])
[1, 3]
Un decorador es esencialmente un envoltorio que toma la función envuelta como argumento y devuelve otra función.
Como se indica en la respuesta aceptada, singledispatch
devuelve un wrapper
que toma el primer argumento como tipo registrado - self
en métodos de instancia.
Como se muestra en esa respuesta, en casos como este puede escribir otro envoltorio para el parche de mono del decorador. Pero este tipo de soluciones hacky no siempre son la mejor opción.
Al igual que con cualquier otra función, puede llamar al contenedor y pasarle los argumentos explícitamente, lo que parece más simple, más plano y más legible para mí si este tipo de sobrecarga de métodos rara vez se realiza en un paquete.
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])
Hay otro módulo, singledispatch
multipledispatch
(no estándar, pero incluido en Anaconda y sin dependencias no estándar) que, como el nombre ya indica y a diferencia de singledispatch
, permite multimétodos.
Además de los objetos Dispatcher
, con la singledispatch
compatible con singledispatch
, proporciona un decorador de dispatch
que oculta la creación y manipulación de estos objetos por parte del usuario.
El decorador de envío usa el nombre de la función para seleccionar el objeto Dispatcher apropiado al que agrega la nueva firma / función. Cuando encuentra un nuevo nombre de función, crea un nuevo objeto Dispatcher y almacena el par Nombre / Distribuidor en un espacio de nombres para referencia futura.
Por ejemplo:
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)