numbers — Numerische abstrakte Basisklassen

Quellcode: Lib/numbers.py


Das Modul numbers (PEP 3141) definiert eine Hierarchie von numerischen abstrakten Basisklassen, die schrittweise mehr Operationen definieren. Keiner der in diesem Modul definierten Typen ist zur Instanziierung vorgesehen.

class numbers.Number

Die Wurzel der numerischen Hierarchie. Wenn Sie nur prüfen möchten, ob ein Argument x eine Zahl ist, ohne sich um die Art zu kümmern, verwenden Sie 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 funktionieren. Dies sind: Konvertierungen nach complex und bool, real, imag, +, -, *, /, **, abs(), conjugate(), == und !=. Alle außer - und != sind abstrakt.

real

Abstrakt. Ruft die reelle Komponente dieser Zahl ab.

imag

Abstrakt. Ruft die imaginäre Komponente dieser Zahl ab.

abstractmethod conjugate()

Abstrakt. Gibt die komplexe Konjugierte zurück. Zum Beispiel: (1+3j).conjugate() == (1-3j).

class numbers.Real

Zu Complex fügt Real die Operationen hinzu, die auf reellen Zahlen funktionieren.

Kurz gesagt, dies sind: eine Konvertierung nach float, math.trunc(), round(), math.floor(), math.ceil(), divmod(), //, %, <, <=, > und >=.

Real stellt auch Standardimplementierungen für complex(), real, imag und conjugate() bereit.

class numbers.Rational

Unterteilt Real und fügt die Eigenschaften numerator und denominator hinzu. Es stellt auch eine Standardimplementierung für float() bereit.

Die Werte numerator und denominator sollten Instanzen von Integral sein und auf ihre niedrigsten Terme reduziert sein, wobei denominator positiv ist.

numerator

Abstrakt. Der Zähler dieser rationalen Zahl.

denominator

Abstrakt. Der Nenner dieser rationalen Zahl.

class numbers.Integral

Unterteilt Rational und fügt eine Konvertierung nach int hinzu. Stellt Standardimplementierungen für float(), numerator und denominator bereit. Fügt abstrakte Methoden für pow() mit Modul und Bit-String-Operationen hinzu: <<, >>, &, ^, |, ~.

Hinweise für Implementierer

Implementierer sollten darauf achten, gleiche Zahlen auch als gleich zu behandeln und ihnen gleiche Hash-Werte zuzuweisen. Dies kann subtil sein, wenn es zwei verschiedene Erweiterungen der reellen Zahlen gibt. Zum Beispiel implementiert fractions.Fraction hash() wie folgt

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

Hinzufügen weiterer numerischer ABCs

Es gibt natürlich weitere mögliche ABCs für Zahlen, und dies wäre eine schlechte Hierarchie, wenn sie die Möglichkeit, diese hinzuzufügen, ausschließen würde. Sie können MyFoo zwischen Complex und Real einfügen mit

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

Implementierung der arithmetischen Operationen

Wir möchten die arithmetischen Operationen so implementieren, dass gemischte Operationen entweder eine Implementierung aufrufen, deren Autor die Typen beider Argumente kannte, oder beide in den nächstgelegenen eingebauten Typ konvertieren und die Operation dort durchführen. Für Unterklassen von Integral bedeutet dies, dass __add__() und __radd__() wie folgt 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

Es gibt 5 verschiedene Fälle für eine gemischte Typoperation auf Unterklassen von Complex. Ich werde mich auf den gesamten obigen Code beziehen, der nicht auf MyIntegral und OtherTypeIKnowAbout verweist, als „Boilerplate“. a ist eine Instanz von A, die eine Unterklasse von Complex ist (a : A <: Complex), und b : B <: Complex. Ich werde a + b betrachten.

  1. Wenn A eine __add__() definiert, die b akzeptiert, ist alles in Ordnung.

  2. Wenn A auf den Boilerplate-Code zurückfällt und einen Wert von __add__() zurückgeben würde, würden wir die Möglichkeit verpassen, dass B eine intelligentere __radd__() definiert, daher sollte der Boilerplate-Code NotImplemented von __add__() zurückgeben. (Oder A implementiert __add__() gar nicht.)

  3. Dann erhält die __radd__() von B eine Chance. Wenn sie a akzeptiert, ist alles in Ordnung.

  4. Wenn sie auf den Boilerplate-Code zurückfällt, gibt es keine weiteren möglichen Methoden mehr zu versuchen, also ist hier die Standardimplementierung angesiedelt.

  5. Wenn B <: A, versucht Python B.__radd__ vor A.__add__. Das ist in Ordnung, da sie mit Wissen über A implementiert wurde, kann sie diese Instanzen behandeln, bevor sie an Complex delegiert.

Wenn A <: Complex und B <: Real ohne weiteres Wissen, dann ist die entsprechende gemeinsame Operation diejenige, die den eingebauten complex-Typ verwendet, und beide __radd__()s landen dort, sodass a+b == b+a.

Da die meisten Operationen auf einem bestimmten Typ sehr ähnlich sein werden, kann es nützlich sein, eine Hilfsfunktion zu definieren, die die Vorwärts- und Rückwärtsinstanzen eines beliebigen Operators erzeugt. Zum Beispiel verwendet fractions.Fraction

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)

# ...