Python 3.4 ajouté la possibilité de définir la surcharge de fonctions avec des méthodes statiques. C'est essentiellement l'exemple de la documentation:
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])
Dans sa forme la plus pure, l'implémentation singledispatch
s'appuie sur le premier argument pour identifier le type, ce qui complique l'extension de cette fonctionnalité aux méthodes d'instance.
Quelqu'un a-t-il des conseils sur la façon d'utiliser (ou de jerry-rig) cette fonctionnalité pour la faire fonctionner avec les méthodes d'instance?
Mise à jour: À partir de Python 3.8,
functools.singledispatchmethod
permet l'envoi unique sur les méthodes, les méthodes de classe, les méthodes abstraites et les méthodes statiques.Pour les anciennes versions Python, voir le reste de cette réponse.
En regardant source pour singledispatch
, nous pouvons voir que le décorateur retourne une fonction wrapper()
, qui sélectionne une fonction à appeler parmi celles enregistrées en fonction du type de args[0]
...
def wrapper(*args, **kw):
return dispatch(args[0].__class__)(*args, **kw)
... ce qui est bien pour une fonction régulière, mais peu utile pour une méthode d'instance, dont le premier argument sera toujours self
.
Nous pouvons cependant écrire un nouveau décorateur methdispatch
, qui s'appuie sur singledispatch
pour faire le gros du travail, mais renvoie à la place une fonction wrapper qui sélectionne la fonction enregistrée à appeler en fonction du type 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
Voici un exemple simple du décorateur utilisé:
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]
Notez que la méthode décorée get()
et la méthode enregistrée dans list
ont un argument initial self
comme d'habitude.
Test de la classe Patchwork
:
>>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a", "c"])
[1, 3]
Un décorateur est essentiellement un wrapper qui prend la fonction encapsulée comme argument et renvoie une autre fonction.
Comme indiqué dans la réponse acceptée, singledispatch
renvoie un wrapper
qui prend le premier argument comme type enregistré - self
dans les méthodes d'instance.
Comme indiqué dans cette réponse, dans des cas comme celui-ci, vous pouvez écrire un autre wrapper pour patcher le décorateur. Mais ce type de correctifs hacky n'est pas toujours la meilleure option.
Comme pour toute autre fonction, vous pouvez appeler l'encapsuleur et lui passer explicitement les arguments, ce qui me semble plus simple, plus plat et plus lisible si ce type de surcharge de méthode n'est que rarement effectué dans un package.
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])
Il existe un autre module, multipledispatch
(non standard mais inclus dans Anaconda et sans dépendances non standard) qui, comme son nom l'indique déjà et contrairement à singledispatch
, permet des méthodes multiples .
En plus des objets Dispatcher
, avec une syntaxe compatible singledispatch
, il fournit un décorateur dispatch
qui masque la création et la manipulation de ces objets à l'utilisateur.
Le décorateur de répartition utilise le nom de la fonction pour sélectionner l'objet Dispatcher approprié auquel il ajoute la nouvelle signature/fonction. Lorsqu'il rencontre un nouveau nom de fonction, il crée un nouvel objet Dispatcher et stocke la paire nom/Dispatcher dans un espace de noms pour référence future.
Par exemple:
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)