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 complex arbeiten. Das sind: Umwandlungen nach complex und bool, 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

Real ergänzt Complex um 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, imag und conjugate() bereit.

class numbers.Rational

Untertyp von Real, ergänzt um die Eigenschaften numerator und denominator. Stellt außerdem eine Standardimplementierung für float() bereit.

Die Werte von numerator und denominator sollten Instanzen von Integral sein und vollständig gekürzt vorliegen, mit positivem denominator.

numerator

Abstrakt. Der Zähler dieser rationalen Zahl.

denominator

Abstrakt. Der Nenner dieser rationalen Zahl.

class numbers.Integral

Untertyp von Rational, ergänzt um eine Umwandlung nach int. Stellt Standardimplementierungen für float(), numerator und denominator bereit. Ergänzt abstrakte Methoden für pow() mit Modulus sowie für Bitoperationen: <<, >>, &, ^, |, ~.

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:

  1. Definiert A ein __add__(), das b annimmt, ist alles in Ordnung.

  2. Fällt A auf den Boilerplate-Code zurück und würde dieser einen Wert aus __add__() zurückgeben, entginge uns die Möglichkeit, dass B ein klügeres __radd__() definiert. Der Boilerplate-Code sollte deshalb NotImplemented aus __add__() zurückgeben. (Oder A setzt __add__() gar nicht um.)

  3. Dann kommt das __radd__() von B zum Zug. Nimmt es a an, ist alles in Ordnung.

  4. Fällt es auf den Boilerplate-Code zurück, gibt es keine weiteren Methoden zum Ausprobieren. Hier gehört deshalb die Standardimplementierung hin.

  5. Ist B ein Untertyp von A, probiert Python B.__radd__ vor A.__add__. Das ist in Ordnung, denn es wurde mit Kenntnis von A umgesetzt und kann diese Instanzen deshalb behandeln, bevor es an Complex weitergibt.

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)

# ...