PEP 303 – Erweitern von divmod() für mehrere Divisoren
- Autor:
- Thomas Bellman <bellman+pep-divmod at lysator.liu.se>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 31. Dez. 2002
- Python-Version:
- 2.3
- Post-History:
Zusammenfassung
Dieser PEP beschreibt eine Erweiterung der eingebauten Funktion divmod(), die es ihr ermöglicht, mehrere Divisoren zu akzeptieren und mehrere Aufrufe von divmod() zu einer einzigen zusammenzufassen.
Bekanntmachung
Dieser PEP wird abgelehnt. Die meisten Verwendungen von verkettetem divmod() beinhalten einen konstanten Modulus (z. B. bei Radix-Konvertierungen) und werden richtigerweise als Schleife codiert. Das Beispiel der Zerlegung von Sekunden in Tage/Stunden/Minuten/Sekunden verallgemeinert sich nicht auf Monate und Jahre; vielmehr wird der gesamte Anwendungsfall flexibler und robuster durch Datums- und Zeitmodule behandelt. Die anderen im PEP erwähnten Anwendungsfälle sind in der realen Codebasis eher selten. Der Vorschlag ist auch in Bezug auf Klarheit und Offensichtlichkeit problematisch. In den Beispielen ist nicht sofort ersichtlich, ob die Argumentreihenfolge korrekt ist oder ob das Ziel-Tupel die richtige Länge hat. Benutzer aus anderen Sprachen werden eher die Standard-Zwei-Argument-Form verstehen, ohne die Dokumentation erneut lesen zu müssen. Siehe python-dev-Diskussion vom 17. Juni 2005 [1].
Spezifikation
Die eingebaute Funktion divmod() würde geändert, um mehrere Divisoren zu akzeptieren, wobei ihre Signatur von divmod(dividend, divisor) zu divmod(dividend, *divisors) geändert wird. Der Dividend wird durch den letzten Divisor geteilt, was einen Quotienten und einen Rest ergibt. Der Quotient wird dann durch den vorletzten Divisor geteilt, was einen neuen Quotienten und Rest ergibt. Dies wird wiederholt, bis alle Divisoren verwendet wurden, und divmod() gibt dann ein Tupel zurück, das aus dem Quotienten des letzten Schritts und den Resten aller Schritte besteht.
Eine Python-Implementierung des neuen divmod()-Verhaltens könnte wie folgt aussehen:
def divmod(dividend, *divisors):
modulos = ()
q = dividend
while divisors:
q, r = q.__divmod__(divisors[-1])
modulos = (r,) + modulos
divisors = divisors[:-1]
return (q,) + modulos
Motivation
Gelegentlich möchte man eine Kette von divmod()-Operationen durchführen und divmod() auf den Quotienten des vorherigen Schritts mit unterschiedlichen Divisoren anwenden. Der häufigste Fall ist wahrscheinlich die Umwandlung einer Anzahl von Sekunden in Wochen, Tage, Stunden, Minuten und Sekunden. Dies würde heute so geschrieben werden:
def secs_to_wdhms(seconds):
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
w, d = divmod(d, 7)
return (w, d, h, m, s)
Dies ist mühsam und fehleranfällig, jedes Mal wenn man es braucht.
Wenn stattdessen die eingebaute Funktion divmod() gemäß dem Vorschlag geändert wird, wird der Code zur Umwandlung von Sekunden in Wochen, Tage, Stunden, Minuten und Sekunden wie folgt:
def secs_to_wdhms(seconds):
w, d, h, m, s = divmod(seconds, 7, 24, 60, 60)
return (w, d, h, m, s)
was einfacher zu tippen, einfacher korrekt zu tippen und einfacher zu lesen ist.
Andere Anwendungen sind:
- Astronomische Winkel (Deklination wird in Grad, Minuten und Sekunden gemessen, Rektaszension wird in Stunden, Minuten und Sekunden gemessen).
- Alte britische Währung (1 Pfund = 20 Schilling, 1 Schilling = 12 Pence).
- Angelsächsische Längeneinheiten: 1 Meile = 1760 Yards, 1 Yard = 3 Fuß, 1 Fuß = 12 Zoll.
- Angelsächsische Gewichtseinheiten: 1 Long Ton = 160 Stone, 1 Stone = 14 Pfund, 1 Pfund = 16 Unzen, 1 Unze = 16 Dram.
- Britische Volumen: 1 Gallone = 4 Quart, 1 Quart = 2 Pint, 1 Pint = 20 Fluid Unzen.
Begründung
Die Idee stammt aus APL, das einen Operator hat, der dies tut. (Ich erinnere mich nicht mehr, wie der Operator aussieht, und er wäre wahrscheinlich ohnehin nicht in ASCII darstellbar.)
Der APL-Operator nimmt eine Liste als sein zweites Operandum, während dieser PEP vorschlägt, dass jeder Divisor ein separates Argument für die Funktion divmod() sein sollte. Dies liegt hauptsächlich daran, dass erwartet wird, dass die häufigsten Verwendungen die Divisoren als Konstanten direkt im Aufruf haben (wie die oben genannten 7, 24, 60, 60), und das Hinzufügen eines Satzes von Klammern oder eckigen Klammern den Aufruf nur unübersichtlich machen würde.
Das Erfordernis einer expliziten Sequenz als zweites Argument für divmod() würde die Abwärtskompatibilität ernsthaft brechen. Es wird als zu hässlich erachtet, divmod() überprüfen zu lassen, ob sein zweites Argument eine Sequenz ist. Und in dem Fall, dass man eine Sequenz hat, die anderswo berechnet wird, ist es einfach genug, stattdessen divmod(x, *divs) zu schreiben.
Das Erfordernis mindestens eines Divisors, d. h. das Ablehnen von divmod(x), wurde in Betracht gezogen, aber es wurde kein guter Grund dafür gefunden, und es wird daher im Namen der Allgemeinheit zugelassen.
Der Aufruf von divmod() ohne Divisoren sollte immer noch ein Tupel (von einem Element) zurückgeben. Code, der divmod() mit einer variablen Anzahl von Divisoren aufruft und somit einen Rückgabewert mit einer „unbekannten“ Anzahl von Elementen erhält, müsste diesen Fall sonst speziell behandeln. Code, der weiß, dass er divmod() ohne Divisoren aufruft, wird als zu albern betrachtet, um eine Sonderbehandlung zu rechtfertigen.
Die Verarbeitung der Divisoren in der anderen Richtung, d. h. die Division mit dem ersten Divisor zuerst statt mit dem letzten Divisor zuerst, wurde in Betracht gezogen. Das Ergebnis kommt jedoch mit dem höchstwertigen Teil zuerst und dem niedrigstwertigen Teil zuletzt (denken Sie an die verketteten divmods als eine Möglichkeit, eine Zahl in „Ziffern“ mit unterschiedlichen Gewichten aufzuteilen), und es ist vernünftig, die Divisoren (Gewichte) in der gleichen Reihenfolge wie das Ergebnis anzugeben.
Die inverse Operation
def inverse_divmod(seq, *factors):
product = seq[0]
for x, y in zip(factors, seq[1:]):
product = product * x + y
return product
könnte ebenfalls nützlich sein. Das Schreiben von
seconds = (((((w * 7) + d) * 24 + h) * 60 + m) * 60 + s)
ist jedoch sowohl zu schreiben als auch zu lesen weniger umständlich als die verketteten divmods. Es wird daher als weniger wichtig erachtet, und seine Einführung kann auf einen eigenen PEP verschoben werden. Außerdem benötigt eine solche Funktion einen guten Namen, und der PEP-Autor hat noch keinen gefunden.
Der Aufruf von divmod("spam") löst keinen Fehler aus, obwohl Strings weder Division noch Modulo unterstützen. Solange wir jedoch nicht das andere Objekt kennen, können wir nicht bestimmen, ob divmod() funktionieren würde oder nicht, und es scheint daher albern, es zu verbieten.
Abwärtskompatibilität
Jedes Modul, das die Funktion divmod() im Modul __builtin__ ersetzt, kann dazu führen, dass andere Module, die die neue Syntax verwenden, fehlschlagen. Es wird erwartet, dass dies sehr selten vorkommt.
Code, der eine TypeError-Ausnahme erwartet, wenn divmod() mit etwas anderem als zwei Argumenten aufgerufen wird, wird fehlschlagen. Dies wird ebenfalls als sehr selten erwartet.
Es sind keine weiteren Probleme bezüglich der Abwärtskompatibilität bekannt.
Referenzimplementierung
Noch nicht fertig, aber es scheint eine ziemlich einfache neue Implementierung der Funktion builtin_divmod() in Python/bltinmodule.c zu sein.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0303.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT