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 verscomplex
etbool
,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
etconjugate()
.
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
:
Si
A
définit une__add__()
qui accepteb
, tout va bien.Si
A
fait appel au code générique et que celui-ci renvoie une valeur de__add__()
, nous manquons la possibilité queB
définisse une__radd__()
plus intelligent, donc le code générique devrait retournerNotImplemented
dans__add__()
(ou alorsA
ne doit pas implémenter__add__()
du tout.)Alors
__radd__()
deB
a une chance. si elle acceptea
, tout va bien.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.
Si
B < : A`
, Python essaieB.__radd__
avantA.__add__
. C’est valable parce qu’elle est implémentée avec la connaissance deA
, 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)
# ...