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

Python Enhancement Proposals

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

Inhaltsverzeichnis

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:

  1. Übernahme eines ternären Operators, der aus Satzzeichen besteht
    <condition> ? <expression1> : <expression2>
    
  2. Ü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>)
    
  3. 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:

  1. Wenn ein Ausdruck Nebeneffekte hat
  2. Wenn einer oder beide der Ausdrücke ressourcenintensiv sind
  3. 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()
  1. readlines() verschiebt den Dateizeiger
  2. für lange Quellen brauchen beide Alternativen Zeit
  3. split() ist nur für Strings gültig und readlines() 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)

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

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