Python: Gibt es einen ternären Operator?
Ja, und die Schreibweise sieht anders aus als in jeder anderen Sprache, die so etwas hat. Dazu zwei ältere Behelfslösungen, die man immer noch findet und die sich anders verhalten, als man denkt.
Die Schreibweise
wert = "erwachsen" if alter >= 18 else "minderjaehrig"
'adult' if age >= 18 else 'minor' -> adult
with age = 12 -> minor
Die Bedingung steht in der Mitte, nicht vorne. Darüber stolpert jeder, der
von C, Java, PHP oder JavaScript kommt, wo es cond ? a : b heißt.
Die Begründung für die Reihenfolge steht in PEP 308: Der übliche Wert steht vorne und das Ganze liest sich wie ein Satz — dieser Wert, wenn die Bedingung gilt, sonst jener.
Es wird nur ein Zweig ausgewertet
Das sollte man einmal sehen, denn genau das macht die Konstruktion sicher:
evaluating TRUE-branch
result = 1
-> the false branch was never evaluated
Deshalb ist das hier auch dann in Ordnung, wenn der Divisor null ist:
ergebnis = "sicher" if b == 0 else a / b
Und deshalb ist der Tupel-Trick nicht dasselbe
Vor Python 2.5 schrieb man das hier:
wert = (falsch_wert, wahr_wert)[bedingung]
Es funktioniert, weil False und True gleich 0 und 1 sind. Aber:
(false_value, true_value)[condition]:
evaluating index 0
evaluating index 1
-> BOTH were evaluated; the tuple has to be built first
Beide Zweige sind gelaufen. Natürlich sind sie das — man kann kein Tupel indizieren, das noch nicht gebaut wurde.
Bei Seiteneffekten ist das Verschwendung. Bei allem, was fehlschlagen kann, ist es ein Fehler:
tuple trick -> ZeroDivisionError: division by zero
ternary -> safe
Die beiden Konstruktionen sind nicht austauschbar. Eine davon wirft.
Die and/or-Variante und wo sie bricht
Die andere Behelfslösung von damals:
wert = bedingung and wahr_wert or falsch_wert
True and 'yes' or 'no' -> yes
False and 'yes' or 'no' -> no
Sieht richtig aus. Jetzt machen wir den Wahr-Wert falsy:
True and '' or 'fallback' -> 'fallback'
'' if True else 'fallback' -> ''
Die Bedingung war True, und trotzdem kam der Ersatzwert heraus. Denn
True and '' ergibt '', und das ist falsy, also geht or zur rechten Seite
weiter.
Es scheitert bei jedem falsy-Wert:
True and 0 or 'FALLBACK' -> 'FALLBACK'
True and '' or 'FALLBACK' -> 'FALLBACK'
True and [] or 'FALLBACK' -> 'FALLBACK'
True and None or 'FALLBACK' -> 'FALLBACK'
True and False or 'FALLBACK' -> 'FALLBACK'
Leere Strings, Null, leere Listen und None sind genau die Werte, die in
echten Daten vorkommen. Wenn dir diese Schreibweise in altem Code begegnet,
behandle sie als Fehler, der nur noch auf die passende Eingabe wartet.
Die Priorität erwischt dich
Der Ausdruck bindet sehr schwach — schwächer als die Grundrechenarten:
1 + 2 if False else 99 -> 99
Nicht 1 + 99. Python hat das als (1 + 2) if False else 99 gelesen, also ist
das ganze 1 + 2 der Wahr-Zweig und übrig bleibt 99.
Was du vermutlich gemeint hast:
1 + (2 if False else 99) -> 100
Als Faustregel: Klammere den Ausdruck, sobald er Teil eines größeren Ausdrucks ist. Das kostet zwei Zeichen und beseitigt die Frage.
Es ist ein Ausdruck und passt dorthin, wo kein if hinpasst
Das ist der eigentliche Grund, es einem vierzeiligen if vorzuziehen:
in a comprehension : [1, 0, 3, 0]
as an argument : 3
in an f-string : 4 items
as a default : default
Den Fall mit dem f-String brauche ich am häufigsten:
f"{n} Eintr{'aege' if n != 1 else 'ag'}"
Ternär oder Filter in einer Comprehension?
Eine häufige Anschlussfrage, und die Antwort hängt an der Position:
[x if x > 0 else 0 for x in values] : [1, 0, 3, 0]
[x for x in values if x > 0] : [1, 3]
Vor dem for ist es ein bedingter Ausdruck und das else ist Pflicht — du
entscheidest, was aus jedem Element wird. Nach dem for ist es eine
Filterbedingung, und ein else ist ein Syntaxfehler, weil es dort nichts
hinzuschreiben gibt:
[x for x in values if x > 0 else 0] -> SyntaxError
Zwei verschiedene Aufgaben: Die erste macht aus vier Elementen vier, die zweite wählt zwei von vieren aus.
Verketten geht, ist aber nicht mehr lesbar
def note(punkte):
return "A" if punkte >= 90 else "B" if punkte >= 80 else "C" if punkte >= 70 else "F"
grade(95) -> A
grade(85) -> B
grade(75) -> C
grade(65) -> F
Es schachtelt nach rechts und tut damit, wonach es aussieht. Ab dem dritten
Zweig würde ich eine if/elif-Kette schreiben — oder
match,
wenn es um Muster geht und nicht um Bereiche.
Und ein Null-Koaleszenz-Operator?
Den gibt es in Python nicht. Am nächsten kommt or, und das ist nicht dasselbe:
value=None 'default' if value is None -> 'default' value or 'default' -> 'default'
value=0 'default' if value is None -> 0 value or 'default' -> 'default'
value='' 'default' if value is None -> '' value or 'default' -> 'default'
value='set' 'default' if value is None -> 'set' value or 'default' -> 'set'
or ersetzt jeden falsy-Wert, nicht nur None. Wenn 0 oder ein leerer
String in deinen Daten gültige Werte sind — eine Stückzahl, ein Rabatt, ein
abgeschicktes Formularfeld — dann wirft x or vorgabe sie stillschweigend weg,
und du willst x if x is not None else vorgabe.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).