PEP 308 – Bedingte Ausdrücke
- Autor:
- Guido van Rossum, Raymond Hettinger
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 07-Feb-2003
- Python-Version:
- 2.5
- Post-History:
- 07-Feb-2003, 11-Feb-2003
Hinzufügen eines bedingten Ausdrucks
Am 29.09.2005 beschloss Guido, bedingte Ausdrücke in der Form „X if C else Y“ hinzuzufügen. [1]
Der motivierende Anwendungsfall war die Verbreitung fehleranfälliger Versuche, denselben Effekt mit „and“ und „or“ zu erzielen. [2]
Frühere Bemühungen der Community, einen bedingten Ausdruck hinzuzufügen, scheiterten an mangelndem Konsens über die beste Syntax. Dieses Problem wurde gelöst, indem einfach das Urteil des BDFL (Benevolent Dictator for Life) eingeholt wurde.
Die Entscheidung wurde durch die Überprüfung bestätigt, wie sich die Syntax bei der Anwendung in der gesamten Standardbibliothek verhielt (diese Überprüfung nähert sich einer Stichprobe realer Anwendungsfälle in einer Vielzahl von Anwendungen, geschrieben von einer Reihe von Programmierern mit unterschiedlichem Hintergrund). [3]
Die folgende Änderung wird an der Grammatik vorgenommen. (Das Symbol `or_test` ist neu, die anderen sind modifiziert.)
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
...
testlist_safe: or_test [(',' or_test)+ [',']]
...
gen_for: 'for' exprlist 'in' or_test [gen_iter]
Die neue Syntax führte fast zu einer geringfügigen syntaktischen Rückwärtsinkompatibilität. In früheren Python-Versionen ist Folgendes legal:
[f for f in lambda x: x, lambda x: x**2 if f(1) == 1]
(D. h. eine Listenkomprehension, bei der die Sequenz nach „in“ eine ungeklammerte Serie von Lambdas ist – oder auch nur ein Lambda.)
In Python 3.0 muss die Serie von Lambdas geklammert werden, z. B.:
[f for f in (lambda x: x, lambda x: x**2) if f(1) == 1]
Dies liegt daran, dass `lambda` weniger stark bindet als der `if-else`-Ausdruck, aber in diesem Kontext konnte das `lambda` bereits von einem `if`-Schlüsselwort gefolgt werden, das noch schwächer bindet (für Details siehe die oben gezeigten Grammatikänderungen).
In Python 2.5 wird jedoch eine etwas andere Grammatik verwendet, die rückwärtskompatibler ist, aber die Grammatik eines `lambda` an dieser Stelle einschränkt, indem sie den Körper des `lambda` verbietet, einen ungeklammerten bedingten Ausdruck zu enthalten. Beispiele:
[f for f in (1, lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, (lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: (x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: x if x >= 0 else -1] # INVALID
Referenzen
Einführung in den früheren Entwurf des PEP (aus historischen Gründen beibehalten)
Anfragen nach einem if-then-else („ternären“) Ausdruck tauchen immer wieder auf comp.lang.python auf. Dieser PEP enthält einen konkreten Vorschlag für eine ziemlich Pythonic-Syntax. Dies ist die einzige Chance für die Community: Wenn dieser PEP mit klarer Mehrheit angenommen wird, wird er in Python 2.4 implementiert. Wenn nicht, wird der PEP mit einer Zusammenfassung der Ablehnungsgründe ergänzt und das Thema sollte besser nicht wieder aufkommen. Obwohl der BDFL Mitautor dieses PEP ist, ist er weder für noch gegen diesen Vorschlag; es liegt an der Community zu entscheiden. Wenn sich die Community nicht entscheiden kann, wird der BDFL den PEP ablehnen.
Nach einer beispiellosen Reaktion der Community (sowohl pro als auch contra wurden sehr gute Argumente vorgebracht) wurde dieser PEP mit Hilfe von Raymond Hettinger überarbeitet. Ohne eine vollständige Versionshistorie durchzugehen, sind die wichtigsten Änderungen eine andere vorgeschlagene Syntax, ein Überblick über vorgeschlagene Alternativen, der Stand der aktuellen Diskussion und eine Diskussion über das Kurzschlussverhalten.
Nach der Diskussion fand eine Abstimmung statt. Obwohl ein allgemeines Interesse an irgendeiner Form von if-then-else-Ausdrücken bestand, konnte kein Format eine Mehrheitsunterstützung auf sich ziehen. Folglich wurde der PEP aufgrund des Fehlens einer überwältigenden Mehrheit für eine Änderung abgelehnt. Außerdem war ein Grundprinzip von Python, den Status quo zu bevorzugen, wenn Zweifel bestehen, welcher Weg eingeschlagen werden soll.
Vorschlag
Die vorgeschlagene Syntax lautet wie folgt:
(if <condition>: <expression1> else: <expression2>)
Beachten Sie, dass die umschließenden Klammern nicht optional sind.
Der resultierende Ausdruck wird wie folgt ausgewertet:
- Zuerst wird <condition> ausgewertet.
- Wenn <condition> wahr ist, wird <expression1> ausgewertet und ist das Ergebnis des Ganzen.
- Wenn <condition> falsch ist, wird <expression2> ausgewertet und ist das Ergebnis des Ganzen.
Eine natürliche Erweiterung dieser Syntax ist die Zulassung eines oder mehrerer „elif“-Teile:
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
Dies wird implementiert, wenn der Vorschlag angenommen wird.
Die Nachteile des Vorschlags sind:
- die erforderlichen Klammern
- Verwechslungsgefahr mit Anweisungssyntax
- zusätzliche semantische Belastung von Doppelpunkten
Beachten Sie, dass höchstens einer von <expression1> und <expression2> ausgewertet wird. Dies wird als „Kurzschlussausdruck“ bezeichnet; es ähnelt der Art und Weise, wie der zweite Operand von „and“ / „or“ nur ausgewertet wird, wenn der erste Operand wahr / falsch ist.
Eine gängige Methode, einen if-then-else-Ausdruck zu emulieren, ist:
<condition> and <expression1> or <expression2>
Dies funktioniert jedoch nicht auf die gleiche Weise: Es gibt <expression2> zurück, wenn <expression1> falsch ist! Sehen Sie sich FAQ 4.16 für Alternativen an, die funktionieren – sie sind jedoch ziemlich hässlich und erfordern viel mehr Aufwand zum Verständnis.
Alternativen
Holger Krekel schlug eine neue, minimalinvasive Variante vor:
<condition> and <expression1> else <expression2>
Das Konzept dahinter ist, dass ein fast vollständiger ternärer Operator mit and/or bereits existiert und dieser Vorschlag die am wenigsten invasive Änderung ist, die ihn vervollständigt. Viele Befragte in der Newsgruppe fanden dies zur angenehmsten Alternative. Allerdings konnten einige Befragte Beispiele posten, die mental schwer zu analysieren waren. Später wurde darauf hingewiesen, dass dieses Konstrukt funktioniert, indem der „else“-Teil die bestehende Bedeutung von „and“ ändert.
Daher gibt es zunehmende Unterstützung für die Variante desselben Konzepts von Christian Tismer:
<condition> then <expression1> else <expression2>
Die Vorteile sind einfache visuelle Analyse, keine erforderlichen Klammern, keine Änderung der Semantik bestehender Schlüsselwörter, nicht so wahrscheinlich wie der Vorschlag, mit Anweisungssyntax verwechselt zu werden, und überlädt den Doppelpunkt nicht weiter. Der Nachteil sind die Implementierungskosten für die Einführung eines neuen Schlüsselworts. Im Gegensatz zu anderen neuen Schlüsselwörtern scheint das Wort „then“ unwahrscheinlich, in bestehenden Programmen als Name verwendet worden zu sein.
—
Viele C-abgeleitete Sprachen verwenden diese Syntax:
<condition> ? <expression1> : <expression2>
Eric Raymond hat dies sogar implementiert. Der BDFL lehnte dies aus mehreren Gründen ab: Der Doppelpunkt hat bereits viele Verwendungen in Python (obwohl er tatsächlich nicht mehrdeutig wäre, da das Fragezeichen einen passenden Doppelpunkt erfordert); für Leute, die nicht an C-abgeleitete Sprachen gewöhnt sind, ist es schwer zu verstehen.
—
Die ursprüngliche Version dieses PEP schlug die folgende Syntax vor:
<expression1> if <condition> else <expression2>
Die außerordentliche Anordnung wurde von vielen Teilnehmern der Diskussion als zu unbequem empfunden; besonders wenn <expression1> lang ist, ist es leicht, die Bedingung beim Überfliegen zu übersehen.
—
Einige haben vorgeschlagen, eine neue eingebaute Funktion hinzuzufügen, anstatt die Syntax der Sprache zu erweitern. Zum Beispiel:
cond(<condition>, <expression1>, <expression2>)
Dies funktioniert nicht so wie eine Syntaxerweiterung, da sowohl expression1 als auch expression2 ausgewertet werden müssen, bevor die Funktion aufgerufen wird. Es gibt keine Möglichkeit, die Auswertung des Ausdrucks abzuschneiden. Es könnte funktionieren, wenn „cond“ (oder ein anderer Name) zu einem Schlüsselwort gemacht würde, aber das hat alle Nachteile der Hinzufügung eines neuen Schlüsselworts, plus verwirrende Syntax: Es **sieht aus wie ein Funktionsaufruf**, sodass ein flüchtiger Leser erwarten könnte, dass sowohl <expression1> als auch <expression2> ausgewertet werden.
Zusammenfassung des aktuellen Stands der Diskussion
Die Gruppen fallen in eines von drei Lager:
- Übernahme eines ternären Operators, der aus Satzzeichen besteht
<condition> ? <expression1> : <expression2>
- Übernahme eines ternären Operators, der aus neuen oder bestehenden Schlüsselwörtern besteht. Die führenden Beispiele sind:
<condition> then <expression1> else <expression2> (if <condition>: <expression1> else: <expression2>)
- Nichts tun.
Die ersten beiden Positionen sind relativ ähnlich.
Manche finden, dass jede Form von Satzzeichen die Sprache kryptischer macht. Andere finden, dass die Satzzeichen-Stil für Ausdrücke und nicht für Anweisungen angemessen ist und einen COBOL-Stil vermeidet: 3 plus 4 mal 5.
Die Anpassung bestehender Schlüsselwörter versucht, die Satzzeichen durch explizite Bedeutung und ein ordentlicheres Aussehen zu verbessern. Der Nachteil ist ein gewisser Verlust der durch Satzzeichenoperatoren gebotenen Ausdrucksstärke. Der andere Nachteil ist, dass sie ein gewisses Maß an Verwirrung zwischen den beiden Bedeutungen und beiden Verwendungen der Schlüsselwörter schafft.
Diese Schwierigkeiten werden durch Optionen überwunden, die neue Schlüsselwörter einführen, deren Implementierung mehr Aufwand erfordert.
Die letzte Position ist das Nichtstun. Argumente dafür sind, die Sprache einfach und prägnant zu halten; die Abwärtskompatibilität zu wahren; und dass jeder Anwendungsfall bereits in Form von „if“ und „else“ ausgedrückt werden kann. Lambda-Ausdrücke sind eine Ausnahme, da sie erfordern, dass das Bedingte in eine separate Funktionsdefinition ausgelagert wird.
Die Argumente gegen das Nichtstun sind, dass die anderen Wahlmöglichkeiten eine größere Ausdrucksstärke ermöglichen und dass die aktuellen Praktiken eine Neigung zu fehlerhaften Verwendungen von „and“, „or“ oder einem ihrer komplexeren, weniger visuell ansprechenden Workarounds zeigen.
Short-Circuit-Verhalten
Der Hauptunterschied zwischen dem ternären Operator und der cond() Funktion besteht darin, dass letztere eine Ausdrucksform bietet, aber keine Kurzschlussbewertung.
Kurzschlussbewertung ist in drei Fällen wünschenswert:
- Wenn ein Ausdruck Nebeneffekte hat
- Wenn einer oder beide der Ausdrücke ressourcenintensiv sind
- Wenn die Bedingung als Schutz für die Gültigkeit des Ausdrucks dient.
# Example where all three reasons apply
data = isinstance(source, file) ? source.readlines()
: source.split()
readlines()verschiebt den Dateizeiger- für lange Quellen brauchen beide Alternativen Zeit
split()ist nur für Strings gültig undreadlines()ist nur für Dateiobjekte gültig.
Befürworter einer cond() Funktion weisen darauf hin, dass die Notwendigkeit der Kurzschlussbewertung selten ist. Beim Durchsuchen bestehender Code-Verzeichnisse fanden sie, dass if/else nicht oft vorkam; und von diesen enthielten nur wenige Ausdrücke, die durch cond() oder einen ternären Operator hätten verbessert werden können; und dass die meisten davon keine Kurzschlussbewertung benötigten. Daher würde cond() für die meisten Bedürfnisse ausreichen und Bemühungen ersparen, die Syntax der Sprache zu ändern.
Weitere unterstützende Beweise stammen aus Scans von C-Codebasen, die zeigen, dass sein ternärer Operator sehr selten verwendet wird (als Prozentsatz der Codezeilen).
Ein Gegenargument zu dieser Analyse ist, dass die Verfügbarkeit eines ternären Operators dem Programmierer in jedem Fall geholfen hat, da er die Notwendigkeit, nach Nebeneffekten zu suchen, ersparte. Weiterhin würde er Fehler ausschließen, die aus entfernten Änderungen entstehen, die Nebeneffekte einführen. Letzterer Fall ist mit dem Aufkommen von Properties, bei denen selbst der Attributzugriff Nebeneffekte haben kann, zu einer Realität geworden.
Die Position des BDFL ist, dass das Kurzschlussverhalten unerlässlich ist, damit ein if-then-else-Konstrukt zur Sprache hinzugefügt werden kann.
Detaillierte Ergebnisse der Abstimmung
Votes rejecting all options: 82
Votes with rank ordering: 436
---
Total votes received: 518
ACCEPT REJECT TOTAL
--------------------- --------------------- -----
Rank1 Rank2 Rank3 Rank1 Rank2 Rank3
Letter
A 51 33 19 18 20 20 161
B 45 46 21 9 24 23 168
C 94 54 29 20 20 18 235
D 71 40 31 5 28 31 206
E 7 7 10 3 5 32
F 14 19 10 7 17 67
G 7 6 10 1 2 4 30
H 20 22 17 4 10 25 98
I 16 20 9 5 5 20 75
J 6 17 5 1 10 39
K 1 6 4 13 24
L 1 2 3 3 9
M 7 3 4 2 5 11 32
N 2 3 4 2 11
O 1 6 5 1 4 9 26
P 5 3 6 1 5 7 27
Q 18 7 15 6 5 11 62
Z 1 1
--- --- --- --- --- --- ----
Total 363 286 202 73 149 230 1303
RejectAll 82 82 82 246
--- --- --- --- --- --- ----
Total 363 286 202 155 231 312 1549
WAHLSCHLÜSSEL
A. x if C else y
B. if C then x else y
C. (if C: x else: y)
D. C ? x : y
E. C ? x ! y
F. cond(C, x, y)
G. C ?? x || y
H. C then x else y
I. x when C else y
J. C ? x else y
K. C -> x else y
L. C -> (x, y)
M. [x if C else y]
N. ifelse C: x else y
O. <if C then x else y>
P. C and x else y
Q. any write-in vote
Details zu Nicht-Listen-Stimmen und deren Rangfolge
3: Q reject y x C elsethenif
2: Q accept (C ? x ! y)
3: Q reject ...
3: Q accept ? C : x : y
3: Q accept (x if C, y otherwise)
3: Q reject ...
3: Q reject NONE
1: Q accept select : (<c1> : <val1>; [<cx> : <valx>; ]* elseval)
2: Q reject if C: t else: f
3: Q accept C selects x else y
2: Q accept iff(C, x, y) # "if-function"
1: Q accept (y, x)[C]
1: Q accept C true: x false: y
3: Q accept C then: x else: y
3: Q reject
3: Q accept (if C: x elif C2: y else: z)
3: Q accept C -> x : y
1: Q accept x (if C), y
1: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
2: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
3: Q accept if C: x else y
1: Q accept (x if C else y)
1: Q accept ifelse(C, x, y)
2: Q reject x or y <- C
1: Q accept (C ? x : y) required parens
1: Q accept iif(C, x, y)
1: Q accept ?(C, x, y)
1: Q accept switch-case
2: Q accept multi-line if/else
1: Q accept C: x else: y
2: Q accept (C): x else: y
3: Q accept if C: x else: y
1: Q accept x if C, else y
1: Q reject choice: c1->a; c2->b; ...; z
3: Q accept [if C then x else y]
3: Q reject no other choice has x as the first element
1: Q accept (x,y) ? C
3: Q accept x if C else y (The "else y" being optional)
1: Q accept (C ? x , y)
1: Q accept any outcome (i.e form or plain rejection) from a usability study
1: Q reject (x if C else y)
1: Q accept (x if C else y)
2: Q reject NONE
3: Q reject NONE
3: Q accept (C ? x else y)
3: Q accept x when C else y
2: Q accept (x if C else y)
2: Q accept cond(C1, x1, C2, x2, C3, x3,...)
1: Q accept (if C1: x elif C2: y else: z)
1: Q reject cond(C, :x, :y)
3: Q accept (C and [x] or [y])[0]
2: Q reject
3: Q reject
3: Q reject all else
1: Q reject no-change
3: Q reject deliberately omitted as I have no interest in any other proposal
2: Q reject (C then x else Y)
1: Q accept if C: x else: y
1: Q reject (if C then x else y)
3: Q reject C?(x, y)
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0308.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT