Comment puis-je utiliser des functools?singledispatch avec les méthodes d'instance?

Python 3.4 ajouté la capacité de définir la surcharge de fonction avec des méthodes statiques. C'est essentiellement l'exemple tiré 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 rend difficile l'extension de cette fonctionnalité aux méthodes d'instance.

est-ce que quelqu'un a des conseils sur la façon d'utiliser (ou jerry-rig) ce fonctionnalité pour le faire fonctionner avec les méthodes d'instance?

31
demandé sur Zero Piraeus 2014-07-07 04:50:41
la source

2 ответов

en regardant la source pour singledispatch , nous pouvons voir que le décorateur renvoie une fonction wrapper() , qui sélectionne une fonction à appeler à partir de ceux enregistrés sur la base 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 pas beaucoup d'utilisation pour une méthode d'instance, dont le premier argument est toujours self .

nous pouvons, cependant, écrire un nouveau décorateur methdispatch , qui s'appuie sur singledispatch pour effectuer le levage lourd, mais renvoie plutôt une fonction d'enrubannage qui sélectionne la fonction enregistrée à appeler sur la base 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 en usage:

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 tant la méthode décorée get() que la méthode enregistrée à list ont un argument initial self comme d'habitude.

essai de la classe Patchwork :

>>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a", "c"])
[1, 3]
44
répondu Zero Piraeus 2014-07-07 06:46:08
la source

un décorateur est essentiellement un wrapper qui prend la fonction wrapped 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 emballage à Monkey patch le décorateur. Mais ce genre de solution macabre n'est pas toujours meilleure option.

comme pour toute autre fonction, vous pouvez appeler le wrapper et lui passer les arguments explicitement, ce qui me semble plus simple, plus flatteur et plus lisible si ce genre de méthode de surcharge n'est que rarement fait dans un paquet.

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 y a un autre module, multipledispatch (non standard mais inclus dans Anaconda et sans aucune dépendance non standard) que, comme le nom l'indique déjà et contrairement à singledispatch , permet multiméthodes.

en plus de Dispatcher objets, avec singledispatch syntaxis compatible, il fournit un dispatch décorateur qui cache la création et la manipulation de ces objets de l'utilisateur.

L'envoi décorateur utilise le nom de la fonction pour sélectionner la approprié Répartiteur objet auquel il ajoute le nouveau la signature de la fonction/. Quand 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)
9
répondu Nuno André 2017-08-28 13:33:29
la source