Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 535 – Rich comparison chaining

Autor:
Alyssa Coghlan <ncoghlan at gmail.com>
Status:
Verschoben
Typ:
Standards Track
Benötigt:
532
Erstellt:
12-Nov-2016
Python-Version:
3.8

Inhaltsverzeichnis

PEP Verschiebung

Weitere Überlegungen zu diesem PEP wurden frühestens bis Python 3.8 zurückgestellt.

Zusammenfassung

Inspiriert von PEP 335 und aufbauend auf dem in PEP 532 beschriebenen Circuit-Breaking-Protokoll schlägt dieses PEP eine Änderung der Definition von verketteten Vergleichen vor, bei der die Vergleichsketten aktualisiert werden, um den linksassoziativen Circuit-Breaking-Operator (else) anstelle des logischen Disjunktionsoperators (and) zu verwenden, wenn der linke Vergleich einen Circuit Breaker als Ergebnis liefert.

Während es einige praktische Komplexitäten gibt, die sich aus der aktuellen Handhabung von eindimensionalen Arrays in NumPy ergeben, sollte diese Änderung ausreichen, um elementweise verkettete Vergleichsoperationen für Matrizen zu ermöglichen, bei denen das Ergebnis eine Matrix boolescher Werte ist, anstatt ValueError auszulösen oder tautologisch True zurückzugeben (was eine nicht-leere Matrix anzeigt).

Beziehung zu anderen PEPs

Dieses PEP wurde aus früheren Iterationen von PEP 532 extrahiert, als Folgeanwendung für das Circuit-Breaking-Protokoll und nicht als wesentlicher Bestandteil seiner Einführung.

Der spezifische Vorschlag in diesem PEP, den elementweisen Vergleichs-Use-Case durch Änderung der semantischen Definition der Vergleichsketten zu behandeln, stammt direkt aus Guidos Ablehnung von PEP 335.

Spezifikation

Ein verketteter Vergleich wie 0 < x < 10, geschrieben als

LEFT_BOUND LEFT_OP EXPR RIGHT_OP RIGHT_BOUND

ist derzeit ungefähr semantisch äquivalent zu

_expr = EXPR
_lhs_result = LEFT_BOUND LEFT_OP _expr
_expr_result = _lhs_result and (_expr RIGHT_OP RIGHT_BOUND)

Unter Verwendung der in PEP 532 eingeführten Circuit-Breaking-Konzepte schlägt dieses PEP vor, die Vergleichsketten zu ändern, um explizit zu prüfen, ob der linke Vergleich einen Circuit Breaker zurückgibt, und falls dies der Fall ist, else anstelle von and zu verwenden, um die Vergleichsketten zu implementieren.

_expr = EXPR
_lhs_result = LEFT_BOUND LEFT_OP _expr
if hasattr(type(_lhs_result), "__else__"):
    _expr_result = _lhs_result else (_expr RIGHT_OP RIGHT_BOUND)
else:
    _expr_result = _lhs_result and (_expr RIGHT_OP RIGHT_BOUND)

Dies ermöglicht es Typen wie NumPy-Arrays, das Verhalten von verketteten Vergleichen zu steuern, indem sie entsprechend definierte Circuit Breaker von Vergleichsoperationen zurückgeben.

Die Erweiterung dieser Logik auf eine beliebige Anzahl von verketteten Vergleichsoperationen wäre dieselbe wie die bestehende Erweiterung für and.

Begründung

Bei der endgültigen Ablehnung von PEP 335 stellte Guido van Rossum fest [1]

Die NumPy-Leute brachten ein etwas separates Problem auf: Für sie ist der häufigste Anwendungsfall verkettete Vergleiche (z. B. A < B < C).

Um diese Beobachtung zu verstehen, müssen wir zuerst untersuchen, wie Vergleiche mit NumPy-Arrays funktionieren

>>> import numpy as np
>>> increasing = np.arange(5)
>>> increasing
array([0, 1, 2, 3, 4])
>>> decreasing = np.arange(4, -1, -1)
>>> decreasing
array([4, 3, 2, 1, 0])
>>> increasing < decreasing
array([ True,  True, False, False, False], dtype=bool)

Hier sehen wir, dass NumPy-Array-Vergleiche standardmäßig elementweise erfolgen, wobei jedes Element im linken Array mit dem entsprechenden Element im rechten Array verglichen wird und eine Matrix mit booleschen Ergebnissen erzeugt wird.

Wenn eine Seite des Vergleichs ein Skalarwert ist, wird er über das Array ausgestrahlt und mit jedem einzelnen Element verglichen.

>>> 0 < increasing
array([False,  True,  True,  True,  True], dtype=bool)
>>> increasing < 4
array([ True,  True,  True,  True, False], dtype=bool)

Dieses Broadcasting-Idiom bricht jedoch zusammen, wenn wir versuchen, verkettete Vergleiche zu verwenden.

>>> 0 < increasing < 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Das Problem ist, dass Python diese verkettete Vergleichsoperation intern implizit in die Form expandiert:

>>> 0 < increasing and increasing < 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Und NumPy erlaubt die implizite Konvertierung in einen booleschen Wert nur für Arrays mit einem einzigen Element, bei denen a.any() und a.all() garantiert das gleiche Ergebnis liefern.

>>> np.array([False]) and np.array([False])
array([False], dtype=bool)
>>> np.array([False]) and np.array([True])
array([False], dtype=bool)
>>> np.array([True]) and np.array([False])
array([False], dtype=bool)
>>> np.array([True]) and np.array([True])
array([ True], dtype=bool)

Der in diesem PEP vorgeschlagene Ansatz würde es ermöglichen, diese Situation zu ändern, indem die Definition von elementweisen Vergleichsoperationen in NumPy aktualisiert wird, um eine dedizierte Unterklasse zurückzugeben, die das neue Circuit-Breaking-Protokoll implementiert und auch die Interpretation des Ergebnis-Arrays in einem booleschen Kontext ändert, um immer False zurückzugeben und somit niemals das Short-Circuiting-Verhalten auszulösen.

class ComparisonResultArray(np.ndarray):
    def __bool__(self):
        # Element-wise comparison chaining never short-circuits
        return False
    def _raise_NotImplementedError(self):
        msg = ("Comparison array truth values are ambiguous outside "
               "chained comparisons. Use a.any() or a.all()")
        raise NotImplementedError(msg)
    def __not__(self):
        self._raise_NotImplementedError()
    def __then__(self, result):
        self._raise_NotImplementedError()
    def __else__(self, result):
        return np.logical_and(self, other.view(ComparisonResultArray))

Mit dieser Änderung könnte das obige Beispiel für verkettete Vergleiche zurückgeben:

>>> 0 < increasing < 4
ComparisonResultArray([ False,  True,  True,  True, False], dtype=bool)

Implementierung

Die tatsächliche Implementierung wurde zurückgestellt, bis ein prinzipielles Interesse an der Idee besteht, die in PEP 532 vorgeschlagenen Änderungen umzusetzen.

…wird noch ausgearbeitet…

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0535.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT