1717 Stimmen

Ersetzungen für switch-Anweisung in Python?

Ich möchte eine Funktion in Python schreiben, die je nach dem Wert eines Eingabeindexes verschiedene feste Werte zurückgibt.

In anderen Sprachen würde ich ein switch o case Anweisung, aber Python scheint nicht über eine switch Aussage. Was sind die empfohlenen Python-Lösungen in diesem Szenario?

77 Stimmen

PEP zum Thema, verfasst von Guido selbst: PEP 3103

28 Stimmen

@chb In diesem PEP erwähnt Guido nicht, dass if/elif-Ketten auch eine klassische Fehlerquelle sind. Es ist ein sehr anfälliges Konstrukt.

15 Stimmen

Was bei allen Lösungen fehlt, ist die Erkennung von doppelte Fallwerte . Als Fail-Fast-Prinzip kann dies ein größerer Verlust sein als die Leistung oder die Fallthrough-Funktion.

6voto

William H. Hooper Punkte 565

Definieren:

def switch1(value, options):
  if value in options:
    options[value]()

ermöglicht Ihnen die Verwendung einer recht einfachen Syntax, bei der die Fälle in einer Map gebündelt sind:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

Ich habe immer wieder versucht, switch so umzudefinieren, dass ich das "lambda:" loswerden konnte, habe aber aufgegeben. Überarbeitung der Definition:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

Ermöglichte es mir, mehrere Fälle demselben Code zuzuordnen und eine Standardoption bereitzustellen:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

Jeder replizierte Fall muss in einem eigenen Wörterbuch stehen; switch() konsolidiert die Wörterbücher, bevor es den Wert nachschlägt. Es ist immer noch hässlicher als ich möchte, aber es hat die grundlegende Effizienz der Verwendung eines Hash-Lookup auf den Ausdruck, anstatt eine Schleife durch alle Schlüssel.

4voto

dccsillag Punkte 787

Wenn Sie sich keine Sorgen machen, dass die Syntaxhervorhebung innerhalb der Case-Suiten verloren geht, können Sie Folgendes tun:

exec {
    1: """
print ('one')
""", 
    2: """
print ('two')
""", 
    3: """
print ('three')
""",
}.get(value, """
print ('None')
""")

value ist der Wert. In C wäre dies der Fall:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

Wir können auch eine Hilfsfunktion für diese Aufgabe erstellen:

def switch(value, cases, default):
    exec cases.get(value, default)

Wir können es also für das Beispiel mit eins, zwei und drei so verwenden:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")

4voto

Alden Punkte 302

Die Zuordnung eines Schlüssels zu einem Code ist nicht wirklich ein Problem, wie die meisten Leute mit dem Diktat gezeigt haben. Der eigentliche Trick ist der Versuch, die ganze Drop-Through- und Break-Sache zu emulieren. Ich glaube nicht, dass ich jemals eine Case-Anweisung geschrieben habe, in der ich dieses "Feature" verwendet habe. Hier ist ein Versuch mit Drop-Through.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

Ich habe mir das wirklich als eine einzige Aussage vorgestellt. Ich hoffe, Sie verzeihen mir die dumme Formatierung.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

Ich hoffe, das ist gültiger Python-Code. Es sollte Ihnen drop through geben. Natürlich könnten die booleschen Prüfungen Ausdrücke sein, und wenn Sie wollten, dass sie faul ausgewertet werden, könnten Sie sie alle in ein Lambda verpacken. Es wäre auch nicht allzu schwer, sie nach der Ausführung einiger Elemente in der Liste zu akzeptieren. Machen Sie einfach das Tupel (bool, bool, function), wobei das zweite bool angibt, ob die Funktion abgebrochen oder abgebrochen werden soll oder nicht.

1 Stimmen

Drop-through ist unstrukturiert. Sie brauchen es nicht, es macht Ihren Code schwieriger zu pflegen, schwieriger, Fehler zu vermeiden, und ist von vielen Codierungsstandards verboten. Es ist ein Überbleibsel von C. C wurde nicht zu 100% strukturiert, sondern ließ drop-through, goto, continue, break. Die meisten seiner Nachfahren haben es kopiert.

4voto

True Punkte 379

Ich habe die folgende Antwort gefunden von Python-Dokumentation am hilfreichsten:

Sie können dies ganz einfach mit einer Folge von if... elif... elif... else . Es gibt einige Vorschläge für die Syntax von Switch-Anweisungen, aber es gibt (noch) keinen Konsens darüber, ob und wie Bereichstests durchgeführt werden sollen. Siehe PEP 275 für vollständige Details und den aktuellen Stand.

Für Fälle, in denen Sie aus einer sehr großen Anzahl von Möglichkeiten wählen müssen, können Sie ein Wörterbuch erstellen, das Fallwerte den aufzurufenden Funktionen zuordnet. Zum Beispiel:

def function_1(...):
    ...

functions = {'a': function_1,
             'b': function_2,
             'c': self.method_1, ...}

func = functions[value]
func()

Für den Aufruf von Methoden auf Objekten können Sie noch mehr vereinfachen, indem Sie die eingebaute Funktion getattr() verwenden, um Methoden mit einem bestimmten Namen abzurufen:

def visit_a(self, ...):
    ...
...

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

Es wird empfohlen, ein Präfix für die Methodennamen zu verwenden, z. B. visit_ in diesem Beispiel. Ohne ein solches Präfix könnte ein Angreifer, wenn die Werte aus einer nicht vertrauenswürdigen Quelle stammen, jede Methode Ihres Objekts aufrufen.

0 Stimmen

PEP 275 scheint abgelehnt worden zu sein.

4voto

abarnert Punkte 332066

Als kleine Abwandlung von Antwort von Mark Biek für ungewöhnliche Fälle wie dieses Duplikat wo der Benutzer eine Reihe von Funktionsaufrufen mit Argumenten zu verzögern hat (und es lohnt sich nicht, eine Reihe von Funktionen out-of-line zu bauen), anstelle von diesem:

d = {
    "a1": lambda: a(1),
    "a2": lambda: a(2),
    "b": lambda: b("foo"),
    "c": lambda: c(),
    "z": lambda: z("bar", 25),
    }
return d[string]()

können Sie dies tun:

d = {
    "a1": (a, 1),
    "a2": (a, 2),
    "b": (b, "foo"),
    "c": (c,)
    "z": (z, "bar", 25),
    }
func, *args = d[string]
return func(*args)

Dies ist sicherlich kürzer, aber ob es besser lesbar ist eine offene Frage


Ich denke, es könnte lesbarer (wenn auch nicht kürzer) sein, wenn man von lambda a partial für diese besondere Verwendung:

d = {
    "a1": partial(a, 1),
    "a2": partial(a, 2),
    "b": partial(b, "foo"),
    "c": c,
    "z": partial(z, "bar", 25),
    }
return d[string]()

was den Vorteil hat, dass es auch mit Schlüsselwortargumenten gut funktioniert:

d = {
    "a1": partial(a, 1),
    "a2": partial(a, 2),
    "b": partial(b, "foo"),
    "c": c,
    "k": partial(k, key=int),
    "z": partial(z, "bar", 25),
    }
return d[string]()

0 Stimmen

Das Problem bei dieser Antwort ist, dass alle Argumente trotzdem sofort ausgewertet werden. Auch wenn dies für den Benutzer zunächst funktioniert, wenn er eines Tages einen Fall hinzufügen muss foo(some_expensive_function(3)) dann ist das ein Problem. Entweder geht das Ganze kaputt, oder es muss vor dem Wörterbuch separat auf diesen Fall getestet werden, was nicht nur seltsam aussieht, sondern einen Leser auf den ersten Blick zu der Annahme verleiten könnte, dass die einzigen in Frage kommenden Optionen im Wörterbuch stehen.

0 Stimmen

@AlexHall Wenn Sie einen Haufen beliebigen Codes haben, der aufgeschoben werden muss, und nicht nur einen Funktionsaufruf, der aufgeschoben werden muss, dann müssen Sie ihn wirklich in eine Funktion verpacken. Und für alles, was nicht so trivial ist wie das Beispiel, das Sie gerade gegeben haben, ist es wahrscheinlich nicht einmal ein Lambda, sondern ein out-of-line named def, das Sie brauchen. Aber ich glaube nicht, dass das ein Fehler in der Sprache ist, denn solcher Code ist wirklich nicht üblich und sollte es auch nicht sein. Wenn Sie ein realistischeres Beispiel haben, gibt es wahrscheinlich einen besseren Weg, es zu refaktorisieren.

0 Stimmen

Sie haben Recht, mein Beispiel lässt sich einfach lösen, indem man schreibt lambda: foo(some_expensive_function(3)) für diesen einen Fall. Ich habe hinzugefügt meine eigene Antwort in diesem Sinne.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X