Diskussion Ternärer Operator: Dictionary- und Tupel-Varianten sinnvoll?

Mat

Aktives Mitglied
Ich wollte gucken, wie ich in Python den ternären Operator schreiben kann und das dann in meine Notizen übertragen.

Dabei bin ich auf die Tupel- und Dictionary-Varianten gestoßen. Und die werden natürlich erstmal komplett ausgewertet, unabhängig davon, welchen Wahrheitswert man kriegt. Wenn man sich das angewöhnt und irgendwann rechenintensive Funktionen hat, ist das sicher ein ganz schöner Stolperstein und Flaschenhals. Deswegen fällt mir keine gute Einsatzmöglichkeit ein.

Sind die sinnvoll einsetzbar oder sollte man sie grundsätzlich vermeiden?
Andere Frage: Gibt es noch weitere Varianten?


Hier meine bisherigen Notizen dazu:​

Python:
# Funktionsaufrufe ausgeben
def infoMsg(msg: str) -> str:
    print(f"call: infoMsg('{msg}')")
    return msg


# Legende:
# + positiv
# * neutral
# - negativ

# Simple Variante
# + inline
print(infoMsg("simpL-wahr") if 8 == 3 else infoMsg("simpL-falsch"))
# > call: infoMsg('simpL-falsch')
# > simpL-falsch

# Tupelzuordnung
# + inline
# * Reihenfolge: (onFalse, onTrue) [bedingung]
# - Referenzen: werden alle aufgerufen
print((infoMsg("tupL-falsch"), infoMsg("tupL-wahr"))[8 == 3])
# > call: infoMsg('tupL-falsch')
# > call: infoMsg('tupL-wahr')
# > tupL-falsch

# Dictionary
# + inline
# + Reihenfolge beliebig
# - Referenzen: werden alle aufgerufen
print({True: infoMsg("dixN-wahr"), False: infoMsg("dixN-falsch")}[8 == 3])
# > call: infoMsg('dixN-wahr')
# > call: infoMsg('dixN-falsch')
# > dixN-falsch

# Lambda
# + inline
# * Reihenfolge: (onFalse, onTrue) [bedingung]
print((lambda: infoMsg("lambда-falsch"), lambda: infoMsg("lambда-wahr"))[8 == 3]())
# > call: infoMsg('lambда-falsch')
# > lambда-falsch

Edit: Beispiele vereinheitlicht
 
Zuletzt bearbeitet:

Lowl3v3l

Aktives Mitglied
devCommunity-Experte
Ich finde den ternären Operator ohnehin nicht so besonders schön, abgesehen vielleicht von Variablenzuweisungen, für mich widerspricht der ziemlich dem Python-Zen. Mal davon abgesehen nutzt du für wirklich große Datenstrukturen, wenn du wirklich mal sowas brauchst, wohl eh eher NumPy oder so, wo du eigene Datenstrukturen hast, die dafür eigene Lösungen vorsehen, denke ich.

Also ich glaub nicht, dass das in der Praxis wirklich ein Problem ist das falsch zu lernen, und wenn doch und es wirklich mal schnell sein muss benutzt du vmtl. eh sowas wie den line_profiler, mit dem das schnell auffällt.

Mfg
 

german

Aktives Mitglied
devCommunity-Experte
Eigentlich hast du deine Frage fast schon selbst beantwortet. Immer true- und false-part auszuwerten ist nicht nur eine Frage von Ressourcenverschwendung und Performanceverlust. Es kann auch crashen wenn eine der beiden Seiten abhängig von der Bedingung nicht ausgewertet werden darf.
Beispiel was mir dazu einfällt (ohne nennenswerte Kenntnisse in Python und ungetestet):
Python:
if p is None:
    val = 4711
else:
    val = p.value
Wenn eine Sprache keinen ternären Operator unterstützt, würde ich nicht versuchen ihn irgendwie nachzubauen.
 

Lowl3v3l

Aktives Mitglied
devCommunity-Experte
Das ist nicht wirklich "Nachbauen", die Notation (<val if true>, <val if false>)[<cond>] ist schon ein Standard-Sprachmittel, wenn auch ein etwas obskures und selten genutztes, ebenso wie <val if true> if <cond> else <val if false>. Das ist schon so gedacht.

Mfg
 

german

Aktives Mitglied
devCommunity-Experte
Nein, ein Nachbauen kann das auch nicht sein, weil es sich anders verhält als der ternäre Operator. Und solang <val if true>und <val if false> Literale sind, ist das auch komplett unbedenklich ¯\_(ツ)_/¯
 

Rushh0ur

Neues Mitglied
Wie wärs mit timeit?

Python:
import timeit
import os
import sys

f = open(os.devnull, "w")
stdout = sys.stdout
sys.stdout = f

# Funktionsaufrufe ausgeben
x = """
def infoMsg(msg: str) -> str:
    print(f"call: infoMsg('{msg}')")
    return msg

"""

# Legende:
# + positiv
# * neutral
# - negativ

# Simple Variante
# + inline
v1 = x + 'print(infoMsg("simpL-wahr") if 8 == 3 else infoMsg("simpL-falsch"))'
# > call: infoMsg('simpL-falsch')
# > simpL-falsch

# Tupelzuordnung
# + inline
# * Reihenfolge: (onFalse, onTrue) [bedingung]
# - Referenzen: werden alle aufgerufen
v2 = x + 'print((infoMsg("tupL-falsch"), infoMsg("tupL-wahr"))[8 == 3])'
# > call: infoMsg('tupL-falsch')
# > call: infoMsg('tupL-wahr')
# > tupL-falsch

# Dictionary
# + inline
# + Reihenfolge beliebig
# - Referenzen: werden alle aufgerufen
v3 = x + 'print({True: infoMsg("dixN-wahr"), False: infoMsg("dixN-falsch")}[8 == 3])'
# > call: infoMsg('dixN-wahr')
# > call: infoMsg('dixN-falsch')
# > dixN-falsch

# Lambda
# + inline
# * Reihenfolge: (onFalse, onTrue) [bedingung]
v4 = x + 'print((lambda: infoMsg("lambda-falsch"), lambda: infoMsg("lambda-wahr"))[8 == 3]())'
# > call: infoMsg('lambда-falsch')
# > lambда-falsch


n = 10000000
print("n = %d" % n, file=stdout)

tv1 = timeit.timeit(stmt=v1, number=n)
print("v1 = %7.3f s, v1.it = %8.1f ns" % (tv1, tv1 / n * 1e9), file=stdout)

tv2 = timeit.timeit(stmt=v2, number=n)
print("v2 = %7.3f s, v2.it = %8.1f ns" % (tv2, tv2 / n * 1e9), file=stdout)

tv3 = timeit.timeit(stmt=v3, number=n)
print("v3 = %7.3f s, v3.it = %8.1f ns" % (tv3, tv3 / n * 1e9), file=stdout)

tv4 = timeit.timeit(stmt=v4, number=n)
print("v4 = %7.3f s, v4.it = %8.1f ns" % (tv4, tv4 / n * 1e9), file=stdout)
Mögliche Ausgabe:
Resultat:
n = 10000000
v1 =  76.750 s, v1.it =   7675.0 ns
v2 = 122.695 s, v2.it =  12269.5 ns
v3 = 125.890 s, v3.it =  12589.0 ns
v4 =  85.832 s, v4.it =   8583.2 ns

Ich bevorzuge meist v1. Für längere switch-case nachbauten tendiere ich zu v3 aka dicts, wobei da gibt es mittlerweile die match Sequenzen....

/Edit: Hatte einen v2/v3 Zahlendreher drin. Nummerieren muss man können. 😅 Hab mich schon gewundert, dass die dict Variante schneller ist als die tuple Variante.
 
Zuletzt bearbeitet:
Oben Unten