PEP 326 – Ein Plädoyer für oberste und unterste Werte
- Autor:
- Josiah Carlson <jcarlson at uci.edu>, Terry Reedy <tjreedy at udel.edu>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 20. Dez 2003
- Python-Version:
- 2.4
- Post-History:
- 20. Dez 2003, 03. Jan 2004, 05. Jan 2004, 07. Jan 2004, 21. Feb 2004
Ergebnisse
Diese PEP wurde vom BDFL abgelehnt [8]. Gemäß der Pseudo-Sunset-Klausel [9] wird PEP 326 ein letztes Mal mit den neuesten Vorschlägen, Codeänderungen usw. aktualisiert und enthält einen Link zu einem Modul [10], das das in der PEP beschriebene Verhalten implementiert. Benutzer, die das in dieser PEP aufgeführte Verhalten wünschen, werden ermutigt, das Modul aus den in Unabhängige Implementierungen? aufgeführten Gründen zu verwenden.
Zusammenfassung
Diese PEP schlägt zwei Singleton-Konstanten vor, die einen oberen und einen unteren [3] Wert darstellen: Max und Min (oder zwei ähnlich suggestive Namen [4]; siehe Offene Punkte).
Wie ihre Namen andeuten, würden Max und Min höher bzw. niedriger als jedes andere Objekt verglichen werden. Ein solches Verhalten führt zu leichter verständlichem Code und weniger Sonderfällen, in denen ein temporärer minimaler oder maximaler Wert erforderlich ist und kein tatsächlicher numerischer minimaler oder maximaler Wert begrenzt ist.
Begründung
Während None als absolutes Minimum verwendet werden kann, das jeder Wert erreichen kann [1], könnte dies in Python 3.0 als veraltet markiert werden [4] und sollte nicht darauf vertraut werden.
Als Ersatz für die Verwendung von None als absolutes Minimum sowie zur Einführung eines absoluten Maximums adressieren die beiden Singleton-Konstanten Max und Min die Anliegen, dass die Konstanten selbsterklärend sind.
Was üblicherweise getan wird, um mit absoluten Minimum- oder Maximumwerten umzugehen, ist, einen Wert festzulegen, der größer ist, als der Autor des Skripts jemals erwartet, dass die Eingabe erreicht, und hofft, dass er nicht erreicht wird.
Guido hat darauf hingewiesen [2], dass es zwei Konstanten gibt, die vorläufig für Höchstwerte verwendet werden können: sys.maxint und positive Fließkomma-Unendlichkeit (1e309 wird zu positiver Unendlichkeit ausgewertet). Jede hat jedoch ihre Nachteile.
- Auf den meisten Architekturen ist sys.maxint beliebig klein (2**31-1 oder 2**63-1) und kann leicht von großen ‘long’-Ganzzahlen oder Fließkommazahlen übertroffen werden.
- Der Vergleich von langen Ganzzahlen, die größer sind als die größte darstellbare Fließkommazahl, mit einer beliebigen Fließkommazahl führt zu einer Ausnahme.
>>> cmp(1.0, 10**309) Traceback (most recent call last): File "<stdin>", line 1, in ? OverflowError: long int too large to convert to float
Auch beim Vergleich großer Ganzzahlen mit positiver Unendlichkeit.
>>> cmp(1e309, 10**309) Traceback (most recent call last): File "<stdin>", line 1, in ? OverflowError: long int too large to convert to float
- Diese gleichen Nachteile bestehen, wenn Zahlen negativ sind.
Die Einführung von Max und Min, die wie oben beschrieben funktionieren, erfordert nicht viel Aufwand. Eine beispielhafte Python-Referenzimplementierung Referenzimplementierung für beide ist enthalten.
Motivation
Es gibt Hunderte von Algorithmen, die damit beginnen, einige Werte auf eine logische (oder numerische) Unendlichkeit oder negative Unendlichkeit zu initialisieren. Python fehlt entweder eine konsistent funktionierende Unendlichkeit oder wirklich der extremste erreichbare Wert. Durch die Hinzufügung von Max und Min hätte Python ein echtes Maximum und Minimum, und solche Algorithmen könnten durch die Reduzierung von Sonderfällen klarer werden.
Beispiele für Max
Beim Testen verschiedener Arten von Servern ist es manchmal notwendig, nur eine bestimmte Anzahl von Clients zu bedienen, bevor der Server beendet wird, was zu Code wie dem folgenden führt:
count = 5
def counts(stop):
i = 0
while i < stop:
yield i
i += 1
for client_number in counts(count):
handle_one_client()
Wenn Max als Wert zugewiesen wird, der für die Anzahl (count) verwendet wird, wird unser Testserver mit minimalem Aufwand zu einem Produktionsserver.
Als weiteres Beispiel im Dijkstra-Algorithmus für den kürzesten Pfad in einem Graphen mit gewichteten Kanten (alle positiv).
- Setze die Abstände zu jedem Knoten im Graphen auf Unendlich.
- Setze den Abstand zum Startknoten auf Null.
- Setze besuchte Knoten als leere Zuordnung.
- Solange der kürzeste Abstand eines noch nicht besuchten Knotens kleiner als Unendlich ist und das Ziel noch nicht besucht wurde.
- Hole den Knoten mit dem kürzesten Abstand.
- Besuche den Knoten.
- Aktualisiere Nachbarknotenabstände und Elternzeiger bei Bedarf für Nachbarknoten, die noch nicht besucht wurden.
- Wenn das Ziel besucht wurde, gehe die Elternzeiger rückwärts durch, um den umgekehrten Pfad zu finden, der genommen werden soll.
Unten ist ein Beispiel für den Dijkstra-Algorithmus für den kürzesten Pfad in einem Graphen mit gewichteten Kanten, der eine Tabelle verwendet (eine schnellere Version, die einen Heap verwendet, ist verfügbar, aber diese Version wird aufgrund ihrer Ähnlichkeit mit der obigen Beschreibung angeboten, die Heap-Version ist über ältere Versionen dieses Dokuments verfügbar).
def DijkstraSP_table(graph, S, T):
table = {} #3
for node in graph.iterkeys():
#(visited, distance, node, parent)
table[node] = (0, Max, node, None) #1
table[S] = (0, 0, S, None) #2
cur = min(table.values()) #4a
while (not cur[0]) and cur[1] < Max: #4
(visited, distance, node, parent) = cur
table[node] = (1, distance, node, parent) #4b
for cdist, child in graph[node]: #4c
ndist = distance+cdist #|
if not table[child][0] and ndist < table[child][1]:#|
table[child] = (0, ndist, child, node) #|_
cur = min(table.values()) #4a
if not table[T][0]:
return None
cur = T #5
path = [T] #|
while table[cur][3] is not None: #|
path.append(table[cur][3]) #|
cur = path[-1] #|
path.reverse() #|
return path #|_
Die Leser sollten beachten, dass das Ersetzen von Max im obigen Code durch eine beliebig große Zahl nicht garantiert, dass der kürzeste Pfad zu einem Knoten diese Zahl niemals überschreitet. Nun, mit einer Einschränkung: Man könnte sicherlich die Gewichte jeder Kante im Graphen aufsummieren und die „beliebig große Zahl“ auf diese Summe setzen. Dies macht den Algorithmus jedoch nicht einfacher zu verstehen und birgt potenzielle Probleme mit numerischen Überläufen.
Gustavo Niemeyer [7] weist darauf hin, dass die Verwendung einer Python-tauglicheren Datenstruktur als Tupel zur Speicherung von Informationen über Knotendistanzen die Lesbarkeit erhöht. Zwei äquivalente Knotenstrukturen (eine mit None, die andere mit Max) und ihre Verwendung in einem entsprechend modifizierten Dijkstra-Algorithmus für den kürzesten Pfad sind unten aufgeführt.
class SuperNode:
def __init__(self, node, parent, distance, visited):
self.node = node
self.parent = parent
self.distance = distance
self.visited = visited
class MaxNode(SuperNode):
def __init__(self, node, parent=None, distance=Max,
visited=False):
SuperNode.__init__(self, node, parent, distance, visited)
def __cmp__(self, other):
return cmp((self.visited, self.distance),
(other.visited, other.distance))
class NoneNode(SuperNode):
def __init__(self, node, parent=None, distance=None,
visited=False):
SuperNode.__init__(self, node, parent, distance, visited)
def __cmp__(self, other):
pair = ((self.visited, self.distance),
(other.visited, other.distance))
if None in (self.distance, other.distance):
return -cmp(*pair)
return cmp(*pair)
def DijkstraSP_table_node(graph, S, T, Node):
table = {} #3
for node in graph.iterkeys():
table[node] = Node(node) #1
table[S] = Node(S, distance=0) #2
cur = min(table.values()) #4a
sentinel = Node(None).distance
while not cur.visited and cur.distance != sentinel: #4
cur.visited = True #4b
for cdist, child in graph[node]: #4c
ndist = distance+cdist #|
if not table[child].visited and\ #|
ndist < table[child].distance: #|
table[child].distance = ndist #|_
cur = min(table.values()) #4a
if not table[T].visited:
return None
cur = T #5
path = [T] #|
while table[cur].parent is not None: #|
path.append(table[cur].parent) #|
cur = path[-1] #|
path.reverse() #|
return path #|_
Im obigen Fall wäre die Übergabe von entweder NoneNode oder MaxNode ausreichend, um entweder None oder Max für die Knotendistanz „Unendlich“ zu verwenden. Beachten Sie den zusätzlichen Sonderfall, der für die Verwendung von None als Sentinel in NoneNode in der __cmp__-Methode erforderlich ist.
Dieses Beispiel hebt die Sonderfallbehandlung hervor, bei der None als Sentinelwert für Höchstwerte „in freier Wildbahn“ verwendet wird, obwohl None selbst kleiner als jedes andere Objekt in der Standarddistribution verglichen wird.
Nebenbei bemerkt der Autor nicht, dass die Verwendung von Knoten als Ersatz für Tupel die Lesbarkeit signifikant, wenn überhaupt, erhöht hat.
Ein Min-Beispiel
Ein Anwendungsbeispiel für Min ist ein Algorithmus, der das folgende Problem löst [5]
Angenommen, Sie erhalten einen gerichteten Graphen, der ein Kommunikationsnetzwerk darstellt. Die Knoten sind die Punkte im Netzwerk, und jede Kante ist ein Kommunikationskanal. Jede Kante(u, v)hat einen zugeordneten Wertr(u, v), mit0 <= r(u, v) <= 1, der die Zuverlässigkeit des Kanals vonunachvdarstellt (d. h. die Wahrscheinlichkeit, dass der Kanal vonunachvnicht ausfällt). Angenommen, die Zuverlässigkeitswahrscheinlichkeiten der Kanäle sind unabhängig. (Dies impliziert, dass die Zuverlässigkeit eines Pfades das Produkt der Zuverlässigkeit der Kanten entlang des Pfades ist.) Nun nehmen Sie zwei Knoten im Graphen,AundB.
Ein solcher Algorithmus ist eine 7-zeilige Modifikation des obigen DijkstraSP_table-Algorithmus (modifizierte Zeilen mit * gekennzeichnet)
def DijkstraSP_table(graph, S, T):
table = {} #3
for node in graph.iterkeys():
#(visited, distance, node, parent)
* table[node] = (0, Min, node, None) #1
* table[S] = (0, 1, S, None) #2
* cur = max(table.values()) #4a
* while (not cur[0]) and cur[1] > Min: #4
(visited, distance, node, parent) = cur
table[node] = (1, distance, node, parent) #4b
for cdist, child in graph[node]: #4c
* ndist = distance*cdist #|
* if not table[child][0] and ndist > table[child][1]:#|
table[child] = (0, ndist, child, node) #|_
* cur = max(table.values()) #4a
if not table[T][0]:
return None
cur = T #5
path = [T] #|
while table[cur][3] is not None: #|
path.append(table[cur][3]) #|
cur = path[-1] #|
path.reverse() #|
return path #|_
Beachten Sie, dass es eine Möglichkeit gibt, den Graphen so zu übersetzen, dass er unverändert an den ursprünglichen DijkstraSP_table-Algorithmus übergeben werden kann. Es gibt auch eine Handvoll einfacher Methoden zur Erstellung von Knotenobjekten, die mit DijkstraSP_table_node funktionieren würden. Solche Übersetzungen werden dem Leser als Übung überlassen.
Andere Beispiele
Andrew P. Lentvorski, Jr. [6] hat darauf hingewiesen, dass verschiedene Datenstrukturen, die Bereichssuche beinhalten, unmittelbaren Nutzen für Max und Min Werte haben. Genauer gesagt: Segmentbäume, Bereichsbäume, k-d-Bäume und Datenbank-Schlüssel.
…Das Problem ist, dass ein Bereich auf einer Seite offen sein kann und nicht immer einen initialisierten Fall hat.Die von mir gesehenen Lösungen sind entweder die Überladung von None als Extremwert oder die Verwendung einer beliebigen Zahl mit großem Betrag. Die Überladung von None bedeutet, dass die eingebauten Funktionen nicht wirklich ohne spezielle Fallprüfungen verwendet werden können, um die undefinierte (oder „falsch definierte“) Ordnung von None zu umgehen. Diese Prüfungen neigen dazu, die gute Leistung von eingebauten Funktionen wie max() und min() zu beeinträchtigen.
Die Wahl einer Zahl mit großem Betrag verzichtet auf die Fähigkeit von Python, mit beliebig großen Ganzzahlen umzugehen, und führt eine potenzielle Quelle für Überlauf-/Unterlauf-Fehler ein.
Weitere Anwendungsbeispiele für Max und Min finden sich im Bereich der Graphenalgorithmen, Bereichssuchalgorithmen, algorithmischen Geometriealgorithmen und anderen.
Unabhängige Implementierungen?
Unabhängige Implementierungen des Min/Max-Konzepts durch Benutzer, die eine solche Funktionalität wünschen, werden wahrscheinlich nicht kompatibel sein und sicherlich inkonsistente Reihenfolgen erzeugen. Die folgenden Beispiele sollen zeigen, wie inkonsistent sie sein können.
- Nehmen wir an, wir hätten ordnungsgemäße separate Implementierungen von MyMax, MyMin, YourMax und YourMin mit demselben Code wie in der Beispielimplementierung erstellt (mit einigen geringfügigen Umbenennungen).
>>> lst = [YourMin, MyMin, MyMin, YourMin, MyMax, YourMin, MyMax, YourMax, MyMax] >>> lst.sort() >>> lst [YourMin, YourMin, MyMin, MyMin, YourMin, MyMax, MyMax, YourMax, MyMax]
Beachten Sie, dass, obwohl alle „Min“-Werte vor den „Max“-Werten stehen, keine Garantie besteht, dass alle Instanzen von YourMin vor MyMin kommen, die Umgekehrte, oder die entsprechenden MyMax und YourMax.
- Das Problem zeigt sich auch bei der Verwendung des heapq-Moduls.
>>> lst = [YourMin, MyMin, MyMin, YourMin, MyMax, YourMin, MyMax, YourMax, MyMax] >>> heapq.heapify(lst) #not needed, but it can't hurt >>> while lst: print heapq.heappop(lst), ... YourMin MyMin YourMin YourMin MyMin MyMax MyMax YourMax MyMax
- Darüber hinaus könnten der findmin_Max-Code und beide Versionen von Dijkstra zu falschen Ausgaben führen, indem sekundäre Versionen von
Maxübergeben werden.
Es wurde darauf hingewiesen [7], dass die nachstehende Referenzimplementierung nicht mit unabhängigen Implementierungen von Max/Min kompatibel wäre. Der Sinn dieser PEP ist die Einführung von „The One True Implementation“ des „The One True Maximum“ und „The One True Minimum“. Benutzerdefinierte Implementierungen von Max und Min Objekten würden somit entmutigt und die Verwendung von „The One True Implementation“ offensichtlich gefördert. Mehrdeutiges Verhalten, das sich aus der Vermischung von benutzerdefinierten Implementierungen von Max und Min mit „The One True Implementation“ ergibt, sollte durch Variablen- und/oder Quellcode-Introspektion leicht erkennbar sein.
Referenzimplementierung
class _ExtremeType(object):
def __init__(self, cmpr, rep):
object.__init__(self)
self._cmpr = cmpr
self._rep = rep
def __cmp__(self, other):
if isinstance(other, self.__class__) and\
other._cmpr == self._cmpr:
return 0
return self._cmpr
def __repr__(self):
return self._rep
Max = _ExtremeType(1, "Max")
Min = _ExtremeType(-1, "Min")
Ergebnisse des Testlaufs
>>> max(Max, 2**65536)
Max
>>> min(Max, 2**65536)
20035299304068464649790...
(lines removed for brevity)
...72339445587895905719156736L
>>> min(Min, -2**65536)
Min
>>> max(Min, -2**65536)
-2003529930406846464979...
(lines removed for brevity)
...072339445587895905719156736L
Offene Fragen
Da die PEP abgelehnt wurde, sind alle offenen Punkte nun geschlossen und belanglos. Das Modul wird die Namen UniversalMaximum und UniversalMinimum verwenden, da es sehr schwierig wäre, zu verwechseln, was jeder von ihnen tut. Für diejenigen, die einen kürzeren Namen benötigen, wird das Umbenennen der Singletons während des Imports vorgeschlagen.
from extremes import UniversalMaximum as uMax,
UniversalMinimum as uMin
Referenzen
Änderungen
- Dieser Abschnitt wurde hinzugefügt.
- Abschnitt Motivation hinzugefügt.
- Markup in reStructuredText geändert.
- Abstrakt, Motivation, Referenzimplementierung und Offene Punkte wurden auf der Grundlage der gleichzeitigen Konzepte von
MaxundMinverdeutlicht. - Zwei Implementierungen des Dijkstra-Algorithmus für den kürzesten Pfad hinzugefügt, die zeigen, wo
Maxzur Beseitigung von Sonderfällen verwendet werden kann. - Ein Anwendungsbeispiel für
Minwurde unter Motivation hinzugefügt. - Ein Beispiel und die Unterüberschrift Andere Beispiele hinzugefügt.
- Referenzimplementierung so geändert, dass beide Elemente aus einer einzigen Klasse/Typ instanziiert werden.
- Eine große Anzahl von offenen Punkten entfernt, die nicht in den Geltungsbereich dieser PEP fallen.
- Ein Beispiel aus Beispiele für Max ersetzt, ein Beispiel in Ein Min-Beispiel geändert.
- Einige Referenzen hinzugefügt.
- BDFL lehnt [8] PEP 326 ab
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0326.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT