2017-03-08 6 views
6

dire que j'ai construit une bibliothèque contenant une Foo classe, avec le soutien de certaines méthodes magiques, disent __add__() et __radd__():Existe-t-il des bonnes pratiques pour les méthodes magiques extensibles en python?

>>> class Foo(object): 
...  def __add__(self, rhs): 
...   print("Foo.__add__", rhs) 
...  def __radd__(self, lhs): 
...   print("Foo.__radd__", lhs) 
... 
>>> foo = Foo() 
>>> foo + 3 
Foo.__add__ 3 
>>> 3 + foo 
Foo.__radd__ 3 

Lors du calcul 3 + foo, python appelle d'abord type(3).__add__(3, foo), mais comme cela retourne NotImplemented, il retombe à type(foo).__radd__(foo, 3):

>>> type(3).__add__(3, foo) 
NotImplemented 

Je voudrais les développeurs de pouvoir construire des bibliothèques au-dessus de ma bibliothèque, par exemple une bibliothèque contenant une classe Bar, et je veux qu'ils aient un contrôle total. En particulier, je veux mettre en œuvre un mécanisme qui permet à l'autre bibliothèque de décider si foo + bar devrait appeler foo.__add__(bar) ou bar.__radd__(foo).

Je vois que NumPy a résolu cela en utilisant le schéma __array_priority__. Mais cela semble causer quelques maux de tête (compte tenu du nombre de questions et de problèmes ouverts à ce sujet). Y a-t-il d'autres bonnes pratiques?

+1

Voulez-vous que cela fonctionne également avec les classes existantes? –

+0

Bien que votre question soit assez intéressante, elle manque de détails. Pouvez-vous décrire plus d'aspects du comportement désiré? –

Répondre

1

Une option populaire est de maintenir une liste des types pris en charge par le LHS, et si le type de l'ERS est pas dans la liste, puis revenez NotImplemented:

class Foo(object): 
    SUPPORTED_TYPES = (int, Foo) 
    def __add__(self, rhs): 
     if isinstance(type(rhs), SUPPORTED_TYPES): 
      [...] # compute self + rhs 
     else: 
      return NotImplemented 

Cela fonctionne bien, sauf rhs est un sous-type intelligent de l'un des SUPPORTED_TYPES: il n'y a aucun moyen d'obtenir le contrôle. En outre, la liste des types de cette manière n'est pas très flexible. Il pourrait être préférable de s'appuyer sur le typage du canard que sur une liste de types codés en dur.

1

Une option simple est d'essayer de laisser le LHS faire ce qu'il doit faire (dans l'exemple ci-dessous appelle la méthode value() du RHS) et dans le cas où elle soulève une exception, l'attraper et retourner NotImplemented:

class Foo(object): 
    [...] 
    def __add__(self, rhs): 
     try: 
      return self._value + rhs.value() 
     except AttributeError: 
      return NotImplemented 

Simple comme peut être, pas besoin de maintenir une liste de SUPPORTED_TYPES. Cependant, il existe un risque que le RHS implémente une méthode value() qui n'a rien à voir avec cette tâche, donc cela peut être un peu risqué. De plus, il n'y a pas de moyen facile pour le rhs d'avoir un contrôle total sur le résultat.

En Python, il est généralement préférable de demander pardon plutôt que de demander la permission, comme ci-dessus, mais vous pouvez préférer vérifier que la rhs a la méthode value():

class Foo(object): 
    def __add__(self, rhs): 
     rhs_value_func = getattr(rhs, "value", None) 
     if rhs_value_func is None: 
      return NotImplemented 
     else: 
      return self._value + rhs_value_func() 
0

Une autre option est de utiliser un attribut tel que __foo_priority__, un peu comme le fait avec son NumPy __array_priority__:

class Foo(object): 
    __foo_priority__ = 0 
    def __add__(self, rhs): 
     delegate = True 
     try: 
      rhs_prio = type(rhs).__foo_priority__ 
      delegate = (self.__foo_priority__ < rhs_prio) 
     except AttributeError: 
      delegate = True 
     if delegate: 
      return NotImplemented 
     else: 
      return self.value_ + rhs.value() 

Cette option est un peu plus compliqué, mais assez flexible. Le seul problème (mineur) avec cette option est qu'il nécessite que le type de rhs ait un attribut supplémentaire, donc il n'y a aucun moyen de donner le contrôle au rhs s'il s'agit d'un type existant sans cet attribut.