numbers — Abstrakte Basisklassen für Zahlen¶
Quellcode: Lib/numbers.py
Das Modul numbers (PEP 3141) definiert eine Hierarchie numerischer abstrakter Basisklassen, die schrittweise mehr Operationen festlegen. Keiner der in diesem Modul definierten Typen ist dafür gedacht, instanziert zu werden.
- class numbers.Number¶
Die Wurzel der Zahlenhierarchie. Willst du nur prüfen, ob ein Argument x eine Zahl ist, ohne dass die Art eine Rolle spielt, verwende
isinstance(x, Number).
Der numerische Turm¶
- class numbers.Complex¶
Unterklassen dieses Typs beschreiben komplexe Zahlen und umfassen die Operationen, die auf dem integrierten Typ
complexarbeiten. Das sind: Umwandlungen nachcomplexundbool,real,imag,+,-,*,/,**,abs(),conjugate(),==und!=. Alle außer-und!=sind abstrakt.- real¶
Abstrakt. Liefert den Realteil dieser Zahl.
- imag¶
Abstrakt. Liefert den Imaginärteil dieser Zahl.
- abstractmethod conjugate()¶
Abstrakt. Gibt die komplex konjugierte Zahl zurück. Zum Beispiel
(1+3j).conjugate() == (1-3j).
- class numbers.Real¶
RealergänztComplexum die Operationen, die auf reellen Zahlen arbeiten.Kurz gesagt sind das: eine Umwandlung nach
float,math.trunc(),round(),math.floor(),math.ceil(),divmod(),//,%,<,<=,>und>=.Real stellt außerdem Standardimplementierungen für
complex(),real,imagundconjugate()bereit.
- class numbers.Rational¶
Untertyp von
Real, ergänzt um die Eigenschaftennumeratorunddenominator. Stellt außerdem eine Standardimplementierung fürfloat()bereit.Die Werte von
numeratorunddenominatorsollten Instanzen vonIntegralsein und vollständig gekürzt vorliegen, mit positivemdenominator.- numerator¶
Abstrakt. Der Zähler dieser rationalen Zahl.
- denominator¶
Abstrakt. Der Nenner dieser rationalen Zahl.
Hinweise für Entwickler eigener Typen¶
Wer eigene Typen umsetzt, sollte darauf achten, dass gleiche Zahlen gleich sind und denselben Hashwert ergeben. Das kann knifflig werden, wenn es zwei verschiedene Erweiterungen der reellen Zahlen gibt. Siehe auch Hashing von numerischen Typen.
Weitere abstrakte Basisklassen für Zahlen ergänzen¶
Natürlich sind weitere abstrakte Basisklassen für Zahlen möglich, und die Hierarchie wäre schlecht, wenn sie das ausschließen würde. Zwischen Complex und Real lässt sich MyFoo so einfügen:
class MyFoo(Complex): ...
MyFoo.register(Real)
Die arithmetischen Operationen umsetzen¶
Die arithmetischen Operationen sollen so umgesetzt werden, dass Operationen mit gemischten Typen entweder eine Implementierung aufrufen, deren Autor die Typen beider Argumente kannte, oder beide in den nächstgelegenen integrierten Typ umwandeln und die Operation dort ausführen. Für Untertypen von Integral heißt das, dass __add__() und __radd__() so definiert werden sollten:
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
Für eine Operation mit gemischten Typen auf Unterklassen von Complex gibt es fünf verschiedene Fälle. Der gesamte Code oben, der sich nicht auf MyIntegral und OtherTypeIKnowAbout bezieht, wird als „Boilerplate“ bezeichnet.
In dieser Beschreibung ist a eine Instanz von A und b eine Instanz von B, wobei A und B beide Untertypen von Complex sind. Betrachte a + b:
Definiert
Aein__add__(), dasbannimmt, ist alles in Ordnung.Fällt
Aauf den Boilerplate-Code zurück und würde dieser einen Wert aus__add__()zurückgeben, entginge uns die Möglichkeit, dassBein klügeres__radd__()definiert. Der Boilerplate-Code sollte deshalbNotImplementedaus__add__()zurückgeben. (OderAsetzt__add__()gar nicht um.)Dann kommt das
__radd__()vonBzum Zug. Nimmt esaan, ist alles in Ordnung.Fällt es auf den Boilerplate-Code zurück, gibt es keine weiteren Methoden zum Ausprobieren. Hier gehört deshalb die Standardimplementierung hin.
Ist
Bein Untertyp vonA, probiert PythonB.__radd__vorA.__add__. Das ist in Ordnung, denn es wurde mit Kenntnis vonAumgesetzt und kann diese Instanzen deshalb behandeln, bevor es anComplexweitergibt.
Ist A ein Untertyp von Complex und B ein Untertyp von Real, ohne dass sie weiteres Wissen teilen, ist die passende gemeinsame Operation diejenige über den integrierten complex. Beide __radd__() landen dort, und damit gilt a+b == b+a.
Weil die meisten Operationen eines gegebenen Typs sich sehr ähnlich sind, kann es nützlich sein, eine Hilfsfunktion zu definieren, die die Vorwärts- und die Rückwärtsvariante eines beliebigen Operators erzeugt. fractions.Fraction macht das zum Beispiel so:
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, Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, 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)
# ...