Ersetzungen für die switch-Anweisung in Python?


1718

Ich möchte eine Funktion in Python schreiben, die verschiedene feste Werte basierend auf dem Wert eines Eingabeindex zurückgibt.

In anderen Sprachen würde ich eine switchoder case-Anweisung verwenden, aber Python scheint keine switchAnweisung zu haben . Was sind die empfohlenen Python-Lösungen in diesem Szenario?


77
Verwandte PEP, verfasst von Guido selbst: PEP 3103
chb

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

15
Bei allen Lösungen fehlt hier die Erkennung doppelter Fallwerte . Als Fail-Fast-Prinzip kann dies ein wichtigerer Verlust sein als die Leistung oder die Durchfallfunktion.
Bob Stein

6
switchist tatsächlich "vielseitiger" als etwas, das unterschiedliche feste Werte basierend auf dem Wert eines Eingabeindex zurückgibt. Es ermöglicht die Ausführung verschiedener Codeteile. Es muss nicht einmal einen Wert zurückgeben. Ich frage mich, ob einige der Antworten hier ein guter Ersatz für eine allgemeine switchAussage sind oder nur für den Fall, dass Werte zurückgegeben werden, ohne dass die Möglichkeit besteht, allgemeine Codeteile auszuführen.
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Fragiles Konstrukt, genau wie eine while-Schleife ein fragiles Konstrukt ist, wenn Sie versuchen, damit zu tun, was für ... in ... tut. Wirst du Programmierer als schwach für die Verwendung von for-Schleifen bezeichnen? Während Schleifen alles sind, was sie tatsächlich brauchen. Aber für Schleifen zeigen Sie klare Absichten, speichern Sie sinnlose Boilerplate und geben Sie die Möglichkeit, leistungsstarke Abstraktionen zu erstellen.
Itsbruce

Antworten:


1486

Sie könnten ein Wörterbuch verwenden:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]

100
Was passiert, wenn x nicht gefunden wird?
Nick

46
@nick: Sie können defaultdict
Eli Bendersky

385
Ich würde empfehlen, das Diktat außerhalb der Funktion zu platzieren, wenn die Leistung ein Problem darstellt, damit das Diktat nicht bei jedem Funktionsaufruf neu erstellt wird
Claudiu,

56
@EliBendersky, Die Verwendung der getMethode wäre collections.defaultdictin diesem Fall wahrscheinlich normaler als die Verwendung von a .
Mike Graham

27
@Nick, Eine Ausnahme wird ausgelöst. Tun Sie dies }.get(x, default)stattdessen, wenn es eine Standardeinstellung geben sollte. (Hinweis: Dies ist viel schöner als das, was passiert, wenn Sie die Standardeinstellung für eine switch-Anweisung
Mike Graham

1375

Wenn Sie Standardeinstellungen wünschen, können Sie die Wörterbuchmethode get(key[, default])verwenden:

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found

11
Was ist, wenn 'a' und 'b' mit 1 übereinstimmen und 'c' und 'd' mit 2 übereinstimmen?
John Mee

13
@JM: Nun, offensichtlich unterstützen Wörterbuch-Lookups keine Fall-Throughs. Sie könnten eine doppelte Wörterbuchsuche durchführen. Dh 'a' & 'b' zeigen auf Antwort1 und 'c' und 'd' zeigen auf Antwort2, die in einem zweiten Wörterbuch enthalten sind.
Nick

3
Dies ist besser, um einen Standardwert zu übergeben
HaTiMSuM

Bei diesem Ansatz gibt es Probleme. Erstens können Sie bei jedem Aufruf von f das Diktat erneut erstellen. Zweitens, wenn Sie einen komplexeren Wert haben, können Sie eine Ausnahme erhalten, z. Wenn x ein Tupel ist und wir so etwas tun wollen x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Dies wird IndexError
Idan Haim Shalom

2
@Idan: Die Frage war, Schalter zu replizieren. Ich bin sicher, ich könnte diesen Code auch brechen, wenn ich versuchen würde, ungerade Werte einzugeben. Ja, es wird neu erstellt, aber es ist einfach zu beheben.
Nick

394

Ich habe es immer so gemacht

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

Von hier


16
Eine großartige Methode, kombiniert mit get (), um Standardwerte zu verarbeiten, ist auch meine beste Wahl
drAlberT

27
In diesem Fall ist es möglicherweise keine gute Idee, Lambda zu verwenden, da Lambda jedes Mal aufgerufen wird, wenn das Wörterbuch erstellt wird.
Asher

13
Leider ist dies das nächste, was die Leute bekommen werden. Methoden, die .get()(wie die derzeit höchsten Antworten) verwenden, müssen alle Möglichkeiten vor dem Versand sorgfältig prüfen und sind daher nicht nur (nicht nur sehr, sondern) äußerst ineffizient und können auch keine Nebenwirkungen haben. Diese Antwort umgeht dieses Problem, ist jedoch ausführlicher. Ich würde nur if / elif / else verwenden, und selbst diese brauchen genauso lange zum Schreiben wie 'case'.
Ninjagecko

13
Würde dies nicht in allen Fällen jedes Mal alle Funktionen / Lambdas auswerten, selbst wenn nur eines der Ergebnisse zurückgegeben wird?
SLF

23
@slf Nein, wenn der Kontrollfluss diesen Code erreicht, erstellt er 3 Funktionen (mithilfe der 3 Lambdas) und erstellt dann ein Wörterbuch mit diesen 3 Funktionen als Werten, die jedoch nicht aufgerufen werden (die Auswertung ist in etwas mehrdeutig) dieser Kontext) zuerst. Dann wird das Wörterbuch über indiziert [value], wodurch nur eine der 3 Funktionen zurückgegeben wird (vorausgesetzt, es valuehandelt sich um eine der 3 Tasten). Die Funktion wurde zu diesem Zeitpunkt noch nicht aufgerufen. Dann (x)ruft die gerade zurück Funktion mit xals Argument (und das Ergebnis geht an result). Die anderen 2 Funktionen werden nicht aufgerufen.
Blubberdiblub

354

Zusätzlich zu den Wörterbuchmethoden (die mir übrigens sehr gut gefallen) können Sie auch if- elif- verwenden else, um die switch/ case/ default-Funktionalität zu erhalten:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

Dies ist natürlich nicht identisch mit switch / case - Sie können nicht so leicht durchfallen wie das Weglassen der breakAnweisung, aber Sie können einen komplizierteren Test durchführen. Die Formatierung ist besser als bei einer Reihe verschachtelter ifs, obwohl dies funktional eher der Fall ist.


51
Ich würde das wirklich bevorzugen, es verwendet ein Standard-Sprachkonstrukt und löst keinen KeyError aus, wenn kein passender Fall gefunden wird
Martyglaubitz

7
Ich habe über das Wörterbuch / den getWeg nachgedacht , aber der Standardweg ist einfach besser lesbar.
Martin Thoma

2
@someuser, aber die Tatsache, dass sie sich "überlappen" können, ist eine Funktion. Sie müssen nur sicherstellen, dass die Reihenfolge die Priorität ist, in der Übereinstimmungen auftreten sollen. Wie für wiederholtes x: mach einfach ein x = the.other.thingvorher. Normalerweise haben Sie ein einzelnes if, mehrere elif und ein einzelnes else, da dies leichter zu verstehen ist.
Matthew Schinckel

7
Schön, dass der "Durchfall ohne Elif" allerdings etwas verwirrend ist. Was ist damit: Vergessen Sie "durchfallen" und akzeptieren Sie es einfach als zwei if/elif/else?
Alois Mahdal

7
Erwähnenswert, bei der Verwendung von Dingen wie x in 'bc', bedenken Sie, dass "" in "bc"ist True.
Lohmar ASHAR

185

Mein Lieblingsrezept für Python für Schalter / Fall ist:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Kurz und einfach für einfache Szenarien.

Vergleichen Sie mit mehr als 11 Zeilen C-Code:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

Sie können sogar mehrere Variablen mithilfe von Tupeln zuweisen:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))

16
Ich finde, dass dies eine robustere Antwort ist als die akzeptierte.
Cerd

3
@some user: C erfordert, dass der Rückgabewert in allen Fällen vom gleichen Typ ist. Python nicht. Ich wollte diese Flexibilität von Python hervorheben, nur für den Fall, dass jemand eine Situation hatte, die eine solche Verwendung rechtfertigte.
ChaimG

3
@ einige Benutzer: Ich persönlich finde {} .get (,) lesbar. Für zusätzliche Lesbarkeit für Python-Anfänger möchten Sie möglicherweise verwenden default = -1; result = choices.get(key, default).
ChaimG

4
Vergleiche mit 1 Zeile von c ++result=key=='a'?1:key==b?2:-1
Jasen

4
@Jasen man kann argumentieren, dass man es auch in einer Zeile von Python machen kann : result = 1 if key == 'a' else (2 if key == 'b' else 'default'). Aber ist der eine Liner lesbar?
ChaimG

101
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Verwendungszweck:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Tests:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.

64
Dies ist nicht bedrohungssicher. Wenn mehrere Schalter gleichzeitig gedrückt werden, nehmen alle Schalter den Wert des letzten Schalters an.
Frankreichscortiz

48
Während @francescortiz wahrscheinlich Thread-sicher bedeutet, ist es auch nicht bedrohungssicher. Es bedroht die Werte der Variablen!
Zizouz212

7
Das Thread-Sicherheitsproblem könnte wahrscheinlich durch Verwendung eines thread-lokalen Speichers umgangen werden . Oder es könnte ganz vermieden werden, indem eine Instanz zurückgegeben und diese Instanz für die Fallvergleiche verwendet wird.
Blubberdiblub

6
@blubberdiblub Aber ist es dann nicht effizienter, eine Standardanweisung zu verwenden if?
wizzwizz4

9
Dies ist auch nicht sicher, wenn es in mehreren Funktionen verwendet wird. Wenn der case(2)Block im angegebenen Beispiel eine andere Funktion aufruft, die switch () verwendet, wird bei case(2, 3, 5, 7)der Suche nach dem nächsten auszuführenden Fall usw. der von der anderen Funktion festgelegte Schalterwert verwendet, nicht der von der aktuellen switch-Anweisung festgelegte .
user9876

52

Mein Lieblingsrezept ist ein wirklich schönes Rezept . Du wirst es wirklich mögen. Es ist das, was ich den tatsächlichen Switch-Case-Anweisungen am nächsten gesehen habe, insbesondere in Bezug auf Funktionen.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

Hier ist ein Beispiel:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"

3
Ich würde ersetzen for case in switch()mit with switch() as casemehr Sinn macht, da es s nur einmal ausgeführt werden .
Ski

4
@Skirmantas: Beachten Sie, dass dies withjedoch nicht möglich ist break, sodass die Fallthrough-Option wegfällt.
Jonas Schäfer

5
Entschuldigung, dass ich mich nicht mehr bemüht habe, dies selbst festzustellen: Eine ähnliche Antwort oben ist nicht threadsicher. Ist das?
David Winiecki

1
@DavidWiniecki Die oben fehlenden Codekomponenten (und möglicherweise das Copyright von activestate) scheinen threadsicher zu sein.
Jasen

Wäre eine andere Version davon so etwas wie if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"?
mpag

51
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


from datetime import datetime

with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4):
        print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes

9
Die Verwendung von Kontextmanagern ist eine gute kreative Lösung. Ich würde empfehlen, ein wenig Erklärung und vielleicht einen Link zu einigen Informationen über Kontextmanager hinzuzufügen, um diesem Beitrag einen Kontext zu geben;)
Will

2
Ich mag if / elif-Ketten nicht besonders, aber dies ist sowohl die kreativste als auch die praktischste aller Lösungen, die ich unter Verwendung der vorhandenen Python-Syntax gesehen habe.
Itsbruce

2
Das ist wirklich schön. Eine vorgeschlagene Verbesserung besteht darin value, der Switch-Klasse eine (öffentliche) Eigenschaft hinzuzufügen, damit Sie auf die Eigenschaft case.valueinnerhalb der Anweisung verweisen können .
Peter

48

Es gibt ein Muster, das ich aus Twisted Python-Code gelernt habe.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

Sie können es jederzeit verwenden, wenn Sie ein Token versenden und erweiterten Code ausführen müssen. In einer Zustandsmaschine hätten Sie state_Methoden und würden versenden self.state. Dieser Schalter kann sauber erweitert werden, indem Sie von der Basisklasse erben und Ihre eigenen do_Methoden definieren. Oft haben Sie nicht einmal do_Methoden in der Basisklasse.

Bearbeiten: wie genau wird das verwendet

Im Falle von SMTP erhalten Sie HELOvon der Leitung. Der relevante Code (von twisted/mail/smtp.py, geändert für unseren Fall) sieht folgendermaßen aus

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

Sie erhalten ' HELO foo.bar.com '(oder Sie erhalten 'QUIT'oder 'RCPT TO: foo'). Dies wird in partsas tokenisiert ['HELO', 'foo.bar.com']. Der tatsächliche Name der Methodensuche wird übernommen parts[0].

(Die ursprüngliche Methode wird auch aufgerufen state_COMMAND, da sie dasselbe Muster zum Implementieren einer Zustandsmaschine verwendet, dh getattr(self, 'state_' + self.mode))


4
Ich sehe den Vorteil dieses Musters nicht darin, nur die Methoden direkt aufzurufen: SMTP (). Do_HELO ('foo.bar.com') OK, es kann allgemeinen Code in der lookupMethod geben, aber da dieser auch überschrieben werden kann In der Unterklasse sehe ich nicht, was Sie durch die Indirektion gewinnen.
Herr Shark

1
Sie würden nicht wissen, welche Methode Sie im Voraus aufrufen sollen, dh 'HELO' stammt von einer Variablen. Ich habe ein Verwendungsbeispiel zum ursprünglichen Beitrag hinzugefügt

Darf ich einfach vorschlagen: eval ('SMTP (). Do_' + Befehl) ('foo.bar.com')
jforberg

8
eval? Ernsthaft? und anstatt eine Methode pro Aufruf zu instanziieren, können wir sie sehr gut einmal instanziieren und in allen Aufrufen verwenden, vorausgesetzt, sie hat keinen internen Status.
Mahesh

1
IMO ist der eigentliche Schlüssel hier das Versenden mit getattr, um eine auszuführende Funktion anzugeben. Wenn sich die Methoden in einem Modul befinden, können Sie getattr (local (), func_name) ausführen, um es abzurufen. Der 'do_'-Teil ist gut für Sicherheit / Fehler, so dass nur Funktionen mit dem Präfix aufgerufen werden können. SMTP selbst ruft lookupMethod auf. Im Idealfall weiß die Außenseite nichts davon. Es ist nicht wirklich sinnvoll, SMTP (). LookupMethod (name) (data) auszuführen. Da sich der Befehl und die Daten in einer Zeichenfolge befinden und SMTP sie analysiert, ist dies sinnvoller. Schließlich hat SMTP wahrscheinlich einen anderen gemeinsamen Status, der rechtfertigt, dass es sich um eine Klasse handelt.
ShawnFumo

27

Angenommen, Sie möchten nicht nur einen Wert zurückgeben, sondern Methoden verwenden, die etwas an einem Objekt ändern. Die Verwendung des hier angegebenen Ansatzes wäre:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

Was hier passiert ist, dass Python alle Methoden im Wörterbuch auswertet. Selbst wenn Ihr Wert 'a' ist, wird das Objekt um x inkrementiert und dekrementiert.

Lösung:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

Sie erhalten also eine Liste mit einer Funktion und ihren Argumenten. Auf diese Weise werden nur der Funktionszeiger und die Argumentliste zurückgegeben und nicht ausgewertet. 'result' wertet dann den zurückgegebenen Funktionsaufruf aus.


23

Ich werde nur meine zwei Cent hier reinwerfen. Der Grund, warum es in Python keine case / switch-Anweisung gibt, ist, dass Python dem Prinzip "Es gibt nur einen richtigen Weg, etwas zu tun" folgt. Natürlich könnten Sie verschiedene Möglichkeiten finden, um die Switch / Case-Funktionalität wiederherzustellen, aber die pythonische Methode, um dies zu erreichen, ist das if / elif-Konstrukt. dh

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

Ich hatte gerade das Gefühl, dass PEP 8 hier ein Nicken verdient hat. Eines der schönen Dinge an Python ist seine Einfachheit und Eleganz. Dies ist größtenteils auf die in PEP 8 festgelegten Grundsätze zurückzuführen, darunter "Es gibt nur einen richtigen Weg, etwas zu tun".


6
Warum hat Python for-Schleifen und while-Schleifen? Alles, was Sie mit einer for-Schleife tun können, können Sie mit einer while-Schleife implementieren.
Itsbruce

1
Wahr. Schalter / Gehäuse werden zu oft von beginnenden Programmierern missbraucht. Was sie wirklich wollen, ist das Strategiemuster .
user228395

Klingt wie Python wünscht, es wäre Clojure
TWR Cole

1
@ TWRCole Ich glaube nicht, Python hat es zuerst gemacht. Python gibt es seit 1990 und Clojure seit 2007.
Taylor

Es gibt nur einen richtigen Weg, um etwas zu tun. Python 2.7 oder Python 3? Lol.
TWR Cole

17

Erweiterung der Idee "Diktieren als Schalter". Wenn Sie einen Standardwert für Ihren Switch verwenden möchten:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'

14
Ich denke, es ist klarer, .get () für das Diktat mit der angegebenen Standardeinstellung zu verwenden. Ich ziehe es vor, Ausnahmen für außergewöhnliche Umstände zu belassen, und es schneidet drei Codezeilen und eine Einrückungsstufe, ohne dunkel zu sein.
Chris B.

10
Dies ist ein außergewöhnlicher Umstand. Es kann ein seltener Umstand sein oder auch nicht, je nach Nützlichkeit, aber es ist definitiv eine Ausnahme (greifen Sie zurück 'default') von der Regel (erhalten Sie etwas von diesem Diktat). Von Natur aus verwenden Python-Programme Ausnahmen im Handumdrehen. Abgesehen davon getkönnte die Verwendung den Code möglicherweise ein bisschen schöner machen.
Mike Graham

16

Wenn Sie einen komplizierten Fallblock haben, können Sie eine Nachschlagetabelle für Funktionswörterbücher verwenden ...

Wenn Sie dies noch nicht getan haben, empfiehlt es sich, in Ihren Debugger einzusteigen und genau anzuzeigen, wie das Wörterbuch die einzelnen Funktionen nachschlägt.

HINWEIS: Verwenden Sie nicht "()" in der Fall- / Wörterbuchsuche, da sonst jede Ihrer Funktionen aufgerufen wird, wenn der Wörterbuch- / Fallblock erstellt wird. Denken Sie daran, da Sie jede Funktion nur einmal mit einer Hash-Suche aufrufen möchten.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()

Ich mag deine Lösung. Aber was ist, wenn ich nur einige Variablen oder Objekte übergeben muss?
Tedo Vrbanec

Dies funktioniert nicht, wenn die Methode Parameter erwartet.
Kulasangar

16

Wenn Sie nach einer zusätzlichen Anweisung als "Schalter" suchen, habe ich ein Python-Modul erstellt, das Python erweitert. Es heißt ESPY als "Enhanced Structure for Python" und ist sowohl für Python 2.x als auch für Python 3.x verfügbar.

In diesem Fall könnte eine switch-Anweisung beispielsweise mit dem folgenden Code ausgeführt werden:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

das kann so verwendet werden:

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

so espy übersetzen Sie es in Python als:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break

Sehr cool, aber wozu steht while True:der generierte Python-Code oben? Es wird getroffen zwangsläufig die breakam unteren Rande des erzeugten Python - Code, so scheint es mir , dass sowohl die while True:und breakentfernt werden kann. contIst ESPY außerdem intelligent genug, um den Namen zu ändern, wenn der Benutzer denselben Namen in seinem eigenen Code verwendet? Auf jeden Fall möchte ich Vanille-Python verwenden, damit ich dies nicht verwende, aber es ist trotzdem cool. +1 für pure Coolness.
ArtOfWarfare

@ArtOfWarfare Der Grund für das while True:und breaks ist, Fall-Through zuzulassen, aber nicht zu erfordern.
Solomon Ucko

Ist dieses Modul noch verfügbar?
Solomon Ucko

15

Ich fand, dass eine gemeinsame Schalterstruktur:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

kann in Python wie folgt ausgedrückt werden:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

oder klarer formatiert:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

Anstatt eine Anweisung zu sein, ist die Python-Version ein Ausdruck, der einen Wert ergibt.


Auch anstelle von ... Parameter ... und p1 (x) wie wäre es mit parameterundp1==parameter
Bob Stein

@ BobStein-VisiBone Hallo, hier ist ein Beispiel, das in meiner Python-Sitzung ausgeführt wird : f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Als ich später anrief f(2), bekam ich 'c'; f(1), 'b'; und f(0), 'a'. P1 (x) bezeichnet ein Prädikat; Solange es zurückkehrt Trueoder False, egal ob es sich um einen Funktionsaufruf oder einen Ausdruck handelt, ist es in Ordnung.
Leo

@ BobStein-VisiBone Ja, du hast recht! Vielen Dank :) Damit der mehrzeilige Ausdruck funktioniert, sollten Klammern gesetzt werden, entweder wie in Ihrem Vorschlag oder wie in meinem modifizierten Beispiel.
Leo

Ausgezeichnet. Jetzt werde ich alle meine Kommentare zu den Eltern löschen .
Bob Stein

15

Die meisten Antworten hier sind ziemlich alt und besonders die akzeptierten, daher scheint es eine Aktualisierung wert zu sein.

Zunächst wird dies in den offiziellen Python-FAQ behandelt und die elifKette für einfache Fälle und die dictfür größere oder komplexere Fälle empfohlen . In visit_einigen Fällen wird auch eine Reihe von Methoden vorgeschlagen (ein Stil, der von vielen Server-Frameworks verwendet wird):

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

In den häufig gestellten Fragen wird auch PEP 275 erwähnt , das geschrieben wurde, um eine endgültige endgültige Entscheidung über das Hinzufügen von Switch-Anweisungen im C-Stil zu erhalten. Dieses PEP wurde jedoch tatsächlich auf Python 3 verschoben und nur als separater Vorschlag, PEP 3103, offiziell abgelehnt . Die Antwort war natürlich nein - aber die beiden PEPs haben Links zu zusätzlichen Informationen, wenn Sie an den Gründen oder der Geschichte interessiert sind.


Eine Sache, die mehrmals auftauchte (und in PEP 275 zu sehen ist, obwohl sie als tatsächliche Empfehlung herausgeschnitten wurde), ist, dass Sie wirklich die Mühe haben, 8 Codezeilen für 4 Fälle zu haben, im Vergleich zu den 6 Zeilen, die Sie in C oder Bash haben würden, können Sie immer schreiben:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

Dies wird von PEP 8 nicht gerade unterstützt, ist aber lesbar und nicht zu unidiomatisch.


In den mehr als einem Jahrzehnt seit der Ablehnung von PEP 3103 wurde das Problem der C-artigen Fallaussagen oder sogar der etwas leistungsfähigeren Version in Go als tot angesehen. Immer wenn jemand Python-Ideen oder -dev anspricht, wird er auf die alte Entscheidung verwiesen.

Die Idee eines vollständigen Mustervergleichs im ML-Stil entsteht jedoch alle paar Jahre, zumal Sprachen wie Swift und Rust ihn übernommen haben. Das Problem ist, dass es ohne algebraische Datentypen schwierig ist, den Mustervergleich optimal zu nutzen. Während Guido mit der Idee einverstanden war, hat niemand einen Vorschlag gemacht, der sehr gut in Python passt. (Sie können meinen Strohmann von 2014 als Beispiel lesen .) Dies könnte sich mit dataclassin 3.7 und einigen sporadischen Vorschlägen für eine leistungsfähigere enumHandhabung von Summentypen oder mit verschiedenen Vorschlägen für verschiedene Arten von aussagelokalen Bindungen (wie PEP 3150 oder die Reihe von Vorschlägen, die derzeit auf -ideas diskutiert werden). Aber bisher hat es nicht.

Gelegentlich gibt es auch Vorschläge für ein Perl 6-Matching, das im Grunde genommen eine Mischung aus allem ist, von elifRegex bis hin zu Single-Dispatch-Typ-Switching.


15

Lösung zum Ausführen von Funktionen:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
    'default':   default,
}.get(option)()

Dabei sind foo1 (), foo2 (), foo3 () und default () Funktionen


1
Ja, zum Beispiel, wenn Ihre Variable Option == "case2" Ihr Ergebnis = foo2 ()
Alejandro Quintanar

und so weiter und so fort.
Alejandro Quintanar

Ja, ich verstehe den Zweck. Aber meine Sorge ist , dass , wenn Sie möchten foo2(), die foo1(), foo3()und default()Funktionen sind alle auch laufen gehen, Dinge bedeuten eine lange Zeit in Anspruch nehmen könnte
Brian Under

1
Lassen Sie das () im Wörterbuch weg. verwenden get(option)(). Problem gelöst.
Timgeb

1
Ausgezeichnet die Verwendung von () ist eine Rostlösung, ich habe einen Kern gemacht, um es zu testen gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Alejandro Quintanar

13

Ich habe die einfache Antwort, nach der ich gesucht habe, in der Google-Suche nicht gefunden. Aber ich habe es trotzdem herausgefunden. Es ist wirklich ganz einfach. Beschlossen, es zu posten und vielleicht ein paar weniger Kratzer auf dem Kopf eines anderen zu verhindern. Der Schlüssel ist einfach "in" und Tupel. Hier ist das Verhalten der switch-Anweisung mit Fall-Through, einschließlich RANDOM-Fall-Through.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

Bietet:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.

Wo genau fällt hier durch?
Jonas Schäfer

Hoppla! Dort fällt es durch, aber ich trage nicht mehr zum Stapelüberlauf bei. Mag sie überhaupt nicht. Ich mag die Beiträge anderer, aber nicht Stackoverflow. Wenn Sie Fall Through für FUNCTIONALITY verwenden, möchten Sie bestimmte Bedingungen in einer case-Anweisung in einem Switch (einem catch all) erfassen, bis Sie eine break-Anweisung in einem Switch erreichen.
JD Graham

2
Hier fallen beide Werte "Hund" und "Katze" durch und werden von der GLEICHEN Funktionalität behandelt, dh sie haben "vier Beine". Es ist ein ABSTRACT-Äquivalent zum Durchfallen und unterschiedliche Werte, die von der SAME-case-Anweisung behandelt werden, wenn eine Unterbrechung auftritt.
JD Graham

@JDGraham Ich denke, Jonas meinte einen weiteren Aspekt des Fallthroughs, der auftritt, wenn Programmierer gelegentlich vergessen, breakam Ende des Codes für a zu schreiben case. Aber ich denke, wir brauchen keinen solchen "Fallthrough" :)
Mikhail Batcer

12

Die Lösungen, die ich benutze:

Eine Kombination aus zwei der hier veröffentlichten Lösungen, die relativ einfach zu lesen ist und Standardeinstellungen unterstützt.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

wo

.get('c', lambda x: x - 22)(23)

schaut "lambda x: x - 2"im Diktat nach und benutzt es mitx=23

.get('xxx', lambda x: x - 22)(44)

findet es nicht im Diktat und verwendet die Standardeinstellung "lambda x: x - 22"mit x=44.


10
# simple case alternative

some_value = 5.0

# this while loop block simulates a case block

# case
while True:

    # case 1
    if some_value > 5:
        print ('Greater than five')
        break

    # case 2
    if some_value == 5:
        print ('Equal to five')
        break

    # else case 3
    print ( 'Must be less than 5')
    break

10
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary

Erwägen Sie eine kurze Beschreibung Ihres Codes und wie er die gestellte Frage löst
Henry Woody

Okay, ich habe jetzt einen Kommentar dazu hinzugefügt.
Vikhyat Agarwal

8

Ich mochte Mark Bies 'Antwort

Da die xVariable zweimal verwendet werden muss, habe ich die Lambda-Funktionen auf parameterlos geändert.

Ich muss mit rennen results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Bearbeiten: Mir ist aufgefallen, dass ich Nonemit Wörterbüchern schreiben kann. Das würde also nachahmenswitch ; case else


Emuliert der Fall None nicht einfach result[None]()?
Bob Stein

Ja genau. Ich meineresult = {'a': 100, None:5000}; result[None]
Guneysos

4
Nur zu überprüfen, ob sich niemand so None:verhält default:.
Bob Stein

7
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Kurz und einfach zu lesen, hat einen Standardwert und unterstützt Ausdrücke sowohl in Bedingungen als auch in Rückgabewerten.

Es ist jedoch weniger effizient als die Lösung mit einem Wörterbuch. Beispielsweise muss Python alle Bedingungen durchsuchen, bevor der Standardwert zurückgegeben wird.


7

Sie können ein versendetes Diktat verwenden:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Ausgabe:

This is case 1
This is case 3
This is case 2
This is case 1

6

Einfach, nicht getestet; Jede Bedingung wird unabhängig ausgewertet: Es gibt keinen Durchfall, aber alle Fälle werden ausgewertet (obwohl der einzuschaltende Ausdruck nur einmal ausgewertet wird), es sei denn, es gibt eine break-Anweisung. Zum Beispiel,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

Drucke Was 1. Was 1 or 2. Was something. (Verdammt! Warum kann ich in Inline-Codeblöcken kein nachfolgendes Leerzeichen haben?), wenn expressionausgewertet wird 1, Was 2.wenn expressionausgewertet 2wird oder Was something.wenn expressionausgewertet wird.


1
Nun, der Fall durch funktioniert, aber nur um zu do_default zu gehen.
Syockit

5

Definieren:

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

ermöglicht es Ihnen, eine ziemlich einfache Syntax zu verwenden, wobei die Fälle in einer Karte gebündelt sind:

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

Ich versuchte immer wieder, den Schalter so neu zu definieren, dass ich das "Lambda:" loswerden konnte, gab aber auf. Definition optimieren:

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

Erlaubte mir, mehrere Fälle demselben Code zuzuordnen und eine Standardoption anzugeben:

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 sich in einem eigenen Wörterbuch befinden. switch () konsolidiert die Wörterbücher, bevor der Wert nachgeschlagen wird. Es ist immer noch hässlicher als ich es gerne hätte, aber es hat die grundlegende Effizienz, eine Hash-Suche für den Ausdruck zu verwenden, anstatt eine Schleife durch alle Schlüssel.


5

Ich denke, der beste Weg ist, die Python-Sprachsprache zu verwenden, um Ihren Code testbar zu halten . Wie in früheren Antworten gezeigt, verwende ich Wörterbücher, um Python-Strukturen und -Sprache zu nutzen und den "Fall" -Code auf verschiedene Arten isoliert zu halten. Unten gibt es eine Klasse, aber Sie können direkt ein Modul, Globals und Funktionen verwenden. Die Klasse verfügt über Methoden, die isoliert getestet werden können . Abhängig von Ihren Anforderungen können Sie auch mit statischen Methoden und Attributen spielen.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

Es ist möglich, diese Methode zu nutzen, indem auch Klassen als Schlüssel von "__choice_table" verwendet werden. Auf diese Weise können Sie den Missbrauch von Instanzen vermeiden und alles sauber und testbar halten.

Angenommen, Sie müssen viele Nachrichten oder Pakete aus dem Netz oder Ihrem MQ verarbeiten. Jedes Paket hat seine eigene Struktur und seinen Verwaltungscode (generisch). Mit dem obigen Code ist es möglich, Folgendes zu tun:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

Die Komplexität verteilt sich also nicht auf den Codefluss, sondern wird in der Codestruktur gerendert .


Wirklich hässlich ... Schaltergehäuse ist beim Lesen so sauber. Kann nicht verstehen, warum es nicht in Python implementiert ist.
jmcollin92

@AndyClifton: Es tut mir leid ... ein Beispiel? Denken Sie an jedes Mal, wenn Sie einen Verzweigungscode für mehrere Entscheidungen benötigen, und Sie können diese Methode anwenden.
J_Zar

@ jmcollin92: Die switch-Anweisung ist bequem, da stimme ich zu. Der Programmierer neigt jedoch dazu, sehr lange Anweisungen und Code zu schreiben, die nicht wiederverwendbar sind. Die Art und Weise, die ich beschrieben habe, ist sauberer zu testen und meiner Meinung nach wiederverwendbarer, IMHO.
J_Zar

@J_Zar: re. Meine Bitte um ein Beispiel: Ja, das verstehe ich, aber ich habe Mühe, dies in den Kontext eines größeren Codeteils zu stellen. Können Sie zeigen, wie ich dies in einer realen Situation verwenden könnte?
Andy Clifton

1
@AndyClifton: Es tut mir leid, ich bin spät dran, aber ich habe einen Beispielfall gepostet.
J_Zar

5

Erweiterung der Antwort von Greg Hewgill - Wir können die Wörterbuchlösung mit einem Dekorateur kapseln:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

Dies kann dann mit dem @case-decorator verwendet werden

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

Die gute Nachricht ist, dass dies bereits im NeoPySwitch- Modul geschehen ist . Einfach mit pip installieren:

pip install NeoPySwitch

5

Eine Lösung, die ich tendenziell benutze und die auch Wörterbücher verwendet, ist:

def decision_time( key, *args, **kwargs):
    def action1()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action2()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action3()
        """This function is a closure - and has access to all the arguments"""
        pass

   return {1:action1, 2:action2, 3:action3}.get(key,default)()

Dies hat den Vorteil, dass nicht jedes Mal versucht wird, die Funktionen zu bewerten, und Sie müssen nur sicherstellen, dass die äußere Funktion alle Informationen erhält, die die inneren Funktionen benötigen.


5

Bisher gab es viele Antworten, die besagten: "Wir haben keinen Schalter in Python, machen Sie es so." Ich möchte jedoch darauf hinweisen, dass die switch-Anweisung selbst ein leicht zu missbrauchendes Konstrukt ist, das in den meisten Fällen vermieden werden kann und sollte, da sie eine verzögerte Programmierung fördert. Ein typisches Beispiel:

def ToUpper(lcChar):
    if (lcChar == 'a' or lcChar == 'A'):
        return 'A'
    elif (lcChar == 'b' or lcChar == 'B'):
        return 'B'
    ...
    elif (lcChar == 'z' or lcChar == 'Z'):
        return 'Z'
    else:
        return None        # or something

Jetzt könnten Sie dies mit einer switch-Anweisung tun (wenn Python eine anbietet), aber Sie würden Ihre Zeit verschwenden, weil es Methoden gibt, die dies gut tun. Oder vielleicht haben Sie etwas weniger Offensichtliches:

def ConvertToReason(code):
    if (code == 200):
        return 'Okay'
    elif (code == 400):
        return 'Bad Request'
    elif (code == 404):
        return 'Not Found'
    else:
        return None

Diese Art von Vorgang kann und sollte jedoch mit einem Wörterbuch ausgeführt werden, da es schneller, weniger komplex, weniger fehleranfällig und kompakter ist.

Und die überwiegende Mehrheit der "Anwendungsfälle" für switch-Anweisungen fällt in einen dieser beiden Fälle. Es gibt nur einen sehr geringen Grund, einen zu verwenden, wenn Sie gründlich über Ihr Problem nachgedacht haben.

Anstatt zu fragen, wie ich in Python wechseln soll, sollten wir uns vielleicht fragen, warum ich in Python wechseln möchte. weil das oft die interessantere Frage ist und oft Fehler in der Gestaltung von allem aufdeckt, was Sie bauen.

Das heißt nicht, dass Schalter auch niemals verwendet werden sollten. Zustandsautomaten, Lexer, Parser und Automaten verwenden sie bis zu einem gewissen Grad. Wenn Sie von einer symmetrischen Eingabe ausgehen und zu einer asymmetrischen Ausgabe wechseln, können sie im Allgemeinen nützlich sein. Sie müssen nur sicherstellen, dass Sie den Schalter nicht als Hammer verwenden, da Sie in Ihrem Code ein paar Nägel sehen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.