"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)".


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: "<<", ">>", "&", "^", "|", "~".


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))


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)


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)

   # ...
