# 9.1. `numbers` — Classes de base abstraites numériques

Code source : Lib/numbers.py

Le module `numbers` (PEP 3141) définit une hiérarchie de classes de base abstraites numériques qui définissent progressivement plus d’opérations. Aucun des types définis dans ce module ne peut être instancié.

class `numbers.``Number`

  La base de la hiérarchie numérique. Si vous voulez juste vérifier qu’un argument x est un nombre, peu importe le type, utilisez `isinstance(x, Number)`.

## 9.1.1. La tour numérique

class `numbers.``Complex`

  Les sous-classes de ce type décrivent des nombres complexes et incluent les opérations qui fonctionnent sur le type natif `complex`. Ce sont : les conversions vers `complex` et `bool`, `real`, `imag`, `+`, `-`, `*`, `/`, `abs()`, `conjugate()`, `==` et `!=`. Toutes sauf `-` et `!=` sont abstraites.

  `real`

    Abstrait. Récupère la partie réelle de ce nombre.

  `imag`

    Abstrait. Retrouve la partie imaginaire de ce nombre.

  abstractmethod `conjugate`()

    Abstrait. Renvoie le complexe conjugué. Par exemple, `(1+3j).conjugate() == (1-3j)`.

class `numbers.``Real`

  `Real` ajoute les opérations qui fonctionnent sur les nombres réels à `Complex`.

  En bref, celles-ci sont : une conversion vers `float`, `math.trunc()`, `round()`, `math.floor()`, `math.ceil()`, `divmod()`, `//`, `%`, `<`, `<=`, `>` et `>=`.

  Real fournit également des valeurs par défaut pour `complex()`, `real`, `imag` et `conjugate()`.

class `numbers.``Rational`

  Dérive `Real` et ajoute les propriétés `numerator` et `denominator` qui doivent être les plus petits possible. Avec celles-ci, il fournit une valeur par défaut pour `float()`.

  `numerator`

    Abstrait.

  `denominator`

    Abstrait.

class `numbers.``Integral`

  Dérive `Rational` et ajoute une conversion en `int`. Fournit des valeurs par défaut pour `float()`, `numerator` et `denominator`. Ajoute des méthodes abstraites pour `**` et les opérations au niveau des bits: `<<`, `>>`, `&`, `^`, `|`, `~`.

## 9.1.2. Notes pour implémenter des types

Les développeurs doivent veiller à ce que des nombres égaux soient bien égaux lors de comparaisons et à ce qu’ils soient hachés aux mêmes valeurs. Cela peut être subtil s’il y a deux dérivations différentes des nombres réels. Par exemple, `fractions.Fraction` implémente `hash()` comme suit :

    def __hash__(self):
        if self.denominator == 1:
            # Get integers right.
            return hash(self.numerator)
        # Expensive check, but definitely correct.
        if self == float(self):
            return hash(float(self))
        else:
            # Use tuple's hash to avoid a high collision rate on
            # simple fractions.
            return hash((self.numerator, self.denominator))

### 9.1.2.1. Ajouter plus d’ABC numériques

Il est bien entendu possible de créer davantage d’ABC pour les nombres et cette hiérarchie serait médiocre si elle excluait la possibilité d’en ajouter. Vous pouvez ajouter `MyFoo` entre `Complex` et `Real` ainsi :

    class MyFoo(Complex): ...
    MyFoo.register(Real)

### 9.1.2.2. Implémentation des opérations arithmétiques

Nous voulons implémenter les opérations arithmétiques de sorte que les opérations en mode mixte appellent une implémentation dont l’auteur connaît les types des deux arguments, ou convertissent chacun dans le type natif le plus proche et effectuent l’opération sur ces types. Pour les sous-types de `Integral`, cela signifie que `__add__()` et `__radd__()` devraient être définis comme suit :

    class MyIntegral(Integral):

        def __add__(self, other):
            if isinstance(other, MyIntegral):
                return do_my_adding_stuff(self, other)
            elif isinstance(other, OtherTypeIKnowAbout):
                return do_my_other_adding_stuff(self, other)
            else:
                return NotImplemented

        def __radd__(self, other):
            if isinstance(other, MyIntegral):
                return do_my_adding_stuff(other, self)
            elif isinstance(other, OtherTypeIKnowAbout):
                return do_my_other_adding_stuff(other, self)
            elif isinstance(other, Integral):
                return int(other) + int(self)
            elif isinstance(other, Real):
                return float(other) + float(self)
            elif isinstance(other, Complex):
                return complex(other) + complex(self)
            else:
                return NotImplemented

Il existe 5 cas différents pour une opération de type mixte sur des sous-classes de `Complex`. Nous nous référerons à tout le code ci-dessus qui ne se réfère pas à `MyIntegral` et `OtherTypeIKnowAbout` comme « expression générique ». `a` est une instance de `A`, qui est un sous-type de `Complex` (`a : A <: Complex`) et `b : B <: Complex`. Considérons `a + b`:

> 1. Si `A` définit une `__add__()` qui accepte `b`, tout va bien.

> 2. Si `A` fait appel au code générique et que celui-ci renvoie une valeur de `__add__()`, nous manquons la possibilité que `B` définisse une `__radd__()` plus intelligent, donc le code générique devrait retourner `NotImplemented` dans `__add__()` (ou alors `A` ne doit pas implémenter `__add__()` du tout.)

> 3. Alors `__radd__()` de `B` a une chance. si elle accepte `a`, tout va bien.

> 4. Si elle fait appel au code générique, il n’y a plus de méthode possible à essayer, c’est donc ici que l’implémentation par défaut intervient.

> 5. Si `B < : A``, Python essaie `B.__radd__` avant `A.__add__`. C’est valable parce qu’elle est implémentée avec la connaissance de `A`, donc elle peut gérer ces instances avant de déléguer à `Complex`.

Si `A <: Complex` et `B <: Real` sans autre information, alors l’opération commune appropriée est celle impliquant `complex` et les deux `__radd__()` atterrissent à cet endroit, donc `a+b == b+a`.

Comme la plupart des opérations sur un type donné seront très similaires, il peut être utile de définir une fonction accessoire qui génère les instances résultantes et inverses d’un opérateur donné. Par exemple, `fractions.Fraction` utilise :

    def _operator_fallbacks(monomorphic_operator, fallback_operator):
        def forward(a, b):
            if isinstance(b, (int, Fraction)):
                return monomorphic_operator(a, b)
            elif isinstance(b, float):
                return fallback_operator(float(a), b)
            elif isinstance(b, complex):
                return fallback_operator(complex(a), b)
            else:
                return NotImplemented
        forward.__name__ = '__' + fallback_operator.__name__ + '__'
        forward.__doc__ = monomorphic_operator.__doc__

        def reverse(b, a):
            if isinstance(a, Rational):
                # Includes ints.
                return monomorphic_operator(a, b)
            elif isinstance(a, numbers.Real):
                return fallback_operator(float(a), float(b))
            elif isinstance(a, numbers.Complex):
                return fallback_operator(complex(a), complex(b))
            else:
                return NotImplemented
        reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
        reverse.__doc__ = monomorphic_operator.__doc__

        return forward, reverse

    def _add(a, b):
        """a + b"""
        return Fraction(a.numerator * b.denominator +
                        b.numerator * a.denominator,
                        a.denominator * b.denominator)

    __add__, __radd__ = _operator_fallbacks(_add, operator.add)

    # ...