À propos de la priorité de surcharge de l'opérateur Python

Aperçu

―― L'autre jour, j'ai eu l'occasion de l'annoncer à PyCon JP 2016, alors je l'ai annoncé.

Question

-Quelle méthode a la priorité sur l'objet A qui définit ** __ add__ ** ou sur l'objet B qui définit ** __radd__ **?

class A:

  def __init__(self, value):
      self.value = value

  def __add__(self, other):
      self.value += other.value
      return self

class B:

  def __init__(self, value):
      self.value = value

  def __radd__(self, other):
      self.value += other.value
      return self

A(1) + B(2) #=>D'un__add__Est-ce que ça marche? B's__radd__Est-ce que ça marche?

Répondre

--Le ** ʻA .__ add__` ** sur la gauche a la priorité et est exécuté.

A(1) + B(2) #=> A(3)

#=>Pour une classe__add__Est implémenté, donc ça marche en premier

Qu'est-ce que ça veut dire?

Documentation a l'explication suivante dans l'explication des méthodes système ** __ rxxx__ **. ..

En appelant ces méthodes, l'opérateur de l'opération arithmétique binomiale était reflété(Fut remplacé)Mettez en œuvre des choses.
Ces fonctions ne sont appelées que si l'opérateur on à gauche ne prend pas en charge l'opération correspondante et les non-opérateurs sont de types différents.
Par exemple, y est__rsub__()S'il s'agit d'une instance d'une classe avec une méthode
Expression x-Lorsque y est évalué, x.__sub__(y)Y lorsque renvoie NotImplemented.__rsub__(x)Est appelé.

# refs http://docs.python.jp/3/reference/datamodel.html#object.__ror__

Il est également écrit comme une note comme suit.

Remarque Le type de l'opérateur à droite est une sous-classe du type de l'opérateur à gauche,
Si une méthode de réflexion est définie pour cette méthode de sous-classe
Cette méthode est appelée avant que la méthode non réfléchissante de l'opérateur de gauche ne soit appelée.
Ce comportement permet aux sous-classes de remplacer l'opération du parent.

Vérifions cela dans l'ordre.

Note


-Non implémenté est "un type intégré à renvoyer lorsqu'une opération de comparaison ou une opération binaire est effectuée sur un type non pris en charge"
- __add__Contre__radd__Une méthode comme celle-ci est appelée une méthode de réflexion.

1. Exécutez «radd» si «add» n'est pas implémenté

A + B

Ce code s'exécute dans l'ordre suivant:

1. 「A.__add__Est exécuté et "B" est ajouté.
2.Si "Non mis en œuvre" est renvoyé en 1, "B".__radd__Pour ajouter "A"

Si ** NotImplemented ** n'est pas retourné aux horaires 1 et 2, respectivement, le résultat ajouté est retourné et le processus se termine. Lorsque ** NotImplemented ** est finalement renvoyé dans le résultat du calcul, ** TypeError ** est renvoyé.

TypeError: unsupported operand type(s) for +: 'A' and 'B'

Vérifions cela avec du code.

class A:

  def __add__(self, other):
      print('1. A.__add__ return NotImplemented')
      return NotImplemented

class B:

  def __radd__(self, other):
      print('2. B.__radd__ return NotImplemented')
      return NotImplemented

A() + B()

Quand j'exécute ce code, cela ressemble à ceci:

Résultat d'exécution


1. A.__add__ return NotImplemented
2. B.__radd__ return NotImplemented
Traceback (most recent call last):
  File "oeprator_loading.py", line 32, in <module>
    A() + B()
TypeError: unsupported operand type(s) for +: 'A' and 'B'

Apparemment, cela fonctionne dans l'ordre que j'ai écrit ci-dessus. Mais où l'exception ** TypeError ** est-elle levée? ** ʻA .__ add__** et **B .__ radd__ ** retournent juste ** NotImplemented` **?

Cherchons donc le contenu du code CPython. Ensuite, j'ai trouvé le code suivant.

static PyObject *
binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
{
    PyObject *result = binary_op1(v, w, op_slot);
    if (result == Py_NotImplemented) {
        Py_DECREF(result);
        return binop_type_error(v, w, op_name);
    }
    return result;
}
// refs  https://github.com/python/cpython/blob/3.6/Objects/abstract.c#L806

Il semble que ** binop_type_error ** crache réellement ** TypeError **, et le flux est le suivant.

1. +Le traitement de l'opérateur est exécuté
2. 「binary_Dans "op1"__add__ or  __radd__Et enfin recevoir "Non mis en œuvre"
3.Lorsque vous recevez «Non mis en œuvre», «binop»_type_"Error" lance "TypeError"

Vous pouvez maintenant voir le flux. Ensuite, vérifiez la partie commentaire

2. S'il s'agit d'une sous-classe, __radd__ sera exécuté en premier

Remarque Le type de l'opérateur à droite est une sous-classe du type de l'opérateur à gauche,
Si une méthode de réflexion est définie pour cette méthode de sous-classe
Cette méthode est appelée avant que la méthode non réfléchissante de l'opérateur de gauche ne soit appelée.
Ce comportement permet aux sous-classes de remplacer l'opération du parent.

Vérifiez si cela fonctionne vraiment comme ça. Considérez le code ci-dessous.

class A:

  def __add__(self, other):
      print('1. A.__add__ return 10')
      return 10

class B:

  def __radd__(self, other):
      print('2. B.__radd__ return NotImplemented')
      return NotImplemented

A() + B()

résultat


1. A.__add__ return 10

Dans ce cas ** ʻA .__ add ** renvoie uniquement **return 10 **, donc ** B . radd __** ne sera pas exécuté. Maintenant héritons de la classe **B ** de la classe ** ʻA **.

class A:

  def __add__(self, other):
      print('1. A.__add__ return 10')
      return 10

class B(A): # <-Hériter d'une classe

  def __radd__(self, other):
      print('2. B.__radd__ return NotImplemented')
      return NotImplemented

A() + B()

résultat


2. B.__radd__ return NotImplemented
1. A.__add__ return 10

Ensuite, ** B .__ radd__ ** est exécuté en premier, et il est confirmé que" Si le côté droit est une sous-classe du côté gauche de l'opérateur **, la méthode de réflexion est préférentiellement exécutée ** ". C'est fait.

Aussi, juste parce que ** B .__ radd__ ** est exécuté préférentiellement, si ** NotImplemented ** est renvoyé, il reviendra à ** ʻA .__ add__` **. compris.

3. Qu'est-ce que «iadd»?

Appelez ces méthodes pour l'affectation arithmétique cumulative(+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=)Implémenter.
Ces méthodes font les opérations en place(Changer de soi)Essaye de faire,
Par conséquent(Ça ne doit pas être, mais ça peut être soi)Doit être retourné.
Si une méthode particulière n'est pas définie, son opération arithmétique cumulative reviendra à une méthode régulière.
Par exemple, x est__iadd__()S'il s'agit d'une instance d'une classe qui a des méthodes, x+=y est x= x.__iadd__(y)Est équivalent à
Sinon, x+cote x et y.__add__(y)Andy.__radd__(x)Seront considérées.
Dans certaines situations, l'affectation cumulative peut entraîner des erreurs inattendues
(Pourquoi est-il ajouté un_tuple[i] += [‘item’]Lève une exception?Prière de se référer à)Mais,
Ce comportement fait en fait partie du comportement du modèle de données.

# refs http://docs.python.jp/3/reference/datamodel.html#object.__ior__

Vous pouvez surcharger le traitement de "** Substitution arithmétique cumulative **". En d'autres termes, vous pouvez personnaliser le fonctionnement de "` ʻA + = B`` ".

Résumé

--Operator Overload est exécuté dans l'ordre de "** 1. Méthode de l'opérateur-> 2. Méthode de réflexion **"

J'ai d'autres questions, mais je les écrirai quelque part plus tard.

Fin

référence

Recommended Posts

À propos de la priorité de surcharge de l'opérateur Python
Mémo opérateur Python 3
Opérateur Trinity (Python)
mémo python utilisant l'opérateur perl-ternaire
Python ou et et opérateur trap
Format de chaîne avec l'opérateur Python%
Python
Python in est aussi un opérateur
[Python] Arrondissez avec juste l'opérateur
Utilisation d'opérateurs non logiques de ou en python