Sostituzioni per l'istruzione switch in Python?


1718

Voglio scrivere una funzione in Python che restituisce diversi valori fissi in base al valore di un indice di input.

In altre lingue userei un'istruzione switcho case, ma Python non sembra avere switchun'istruzione. Quali sono le soluzioni Python consigliate in questo scenario?


77
PEP correlato, scritto dallo stesso Guido: PEP 3103
chb,

28
@chb In quel PEP, Guido non menziona che le catene if / elif sono anche una classica fonte di errore. È un costrutto molto fragile.
itsbruce

15
Mancare da tutte le soluzioni qui è il rilevamento di valori di casi duplicati . Come principio fail-fast, questa potrebbe essere una perdita più importante delle prestazioni o della funzionalità fallthrough.
Bob Stein,

6
switchè in realtà più "versatile" di qualcosa che restituisce valori fissi diversi in base al valore di un indice di input. Permette l'esecuzione di diversi pezzi di codice. In realtà non è nemmeno necessario restituire un valore. Mi chiedo se alcune delle risposte qui siano buoni sostituti di una switchdichiarazione generale , o solo nel caso di restituire valori senza possibilità di eseguire parti di codice generali.
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Costrutto fragile, proprio come un ciclo while è un costrutto fragile se provi ad usarlo per fare ciò per ... in ... fa. Chiamerai programmatori deboli per l'utilizzo di loop? Mentre i loop sono tutto ciò di cui hanno effettivamente bisogno. Ma i loop mostrano un chiaro intento, risparmiano inutili scaldabagni e danno l'opportunità di creare potenti astrazioni.
itsbruce,

Risposte:


1486

Puoi usare un dizionario:

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

100
Cosa succede se x non viene trovato?
Nick,

46
@nick: puoi usare defaultdict
Eli Bendersky il

385
Consiglio di mettere il dict al di fuori della funzione se la performance è un problema, quindi non ricostruisce il dict su ogni chiamata di funzione
Claudiu,

56
@EliBendersky, l'utilizzo del getmetodo sarebbe probabilmente più normale rispetto all'utilizzo di a collections.defaultdictin questo caso.
Mike Graham,

27
@Nick, viene generata un'eccezione: esegui }.get(x, default)invece se deve essere presente un valore predefinito. (Nota: è molto più bello di quello che succede se si lascia l'impostazione predefinita su un'istruzione switch!)
Mike Graham,

1374

Se desideri impostazioni predefinite, puoi utilizzare il get(key[, default])metodo del dizionario :

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

11
Cosa succede se 'a' e 'b' corrispondono a 1, e 'c' e 'd' corrispondono a 2?
John Mee,

13
@JM: Beh, ovviamente le ricerche nel dizionario non supportano fall-through. Potresti fare una doppia ricerca nel dizionario. Vale a dire 'a' & 'b' indicano la risposta1 e 'c' e 'd' indicano la risposta2, che sono contenuti in un secondo dizionario.
Nick,

3
è meglio passare un valore predefinito
HaTiMSuM,

Esistono problemi con questo approccio, prima ogni volta che chiami se hai intenzione di creare di nuovo il dict in secondo luogo se hai un valore più complesso puoi ottenere eccezioni es. se x è una tupla e vogliamo fare qualcosa del genere x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Questo aumenterà IndexError
Idan Haim Shalom il

2
@Idan: la domanda era replicare switch. Sono sicuro che potrei anche rompere questo codice se provassi a inserire valori dispari. Sì, verrà ricreato, ma è semplice da risolvere.
Nick,

394

Mi è sempre piaciuto farlo in questo modo

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

Da qui


16
ottimo metodo, combinato con get () per gestire il default è anche la mia scelta migliore
drAlberT

27
forse non è una buona idea usare lambda in questo caso perché lambda viene effettivamente chiamato ogni volta che viene creato il dizionario.
Asher,

13
Purtroppo questa è la gente più vicina che otterrà. I metodi che utilizzano .get()(come le attuali risposte più alte) dovranno valutare con entusiasmo tutte le possibilità prima del dispacciamento, e quindi non solo sono (non solo molto, ma) estremamente inefficienti e non possono avere effetti collaterali; questa risposta aggira quel problema, ma è più dettagliata. Vorrei solo usare if / elif / else, e anche quelli impiegano tanto tempo a scrivere come 'case'.
ninjagecko,

13
questo non valuterà tutte le funzioni / lambda ogni volta in tutti i casi, anche se restituisce solo uno dei risultati?
slf,

23
@slf No, quando il flusso di controllo raggiunge quel pezzo di codice, costruirà 3 funzioni (attraverso l'uso delle 3 lambda) e quindi costruirà un dizionario con quelle 3 funzioni come valori, ma rimangono non richiamati ( valutare è leggermente ambiguo in quel contesto) all'inizio. Quindi il dizionario viene indicizzato tramite [value], che restituirà solo una delle 3 funzioni (supponendo che valuesia uno dei 3 tasti). La funzione non è stata ancora chiamata a quel punto. Quindi (x)chiama la funzione appena restituita con xcome argomento (e il risultato va a result). Le altre 2 funzioni non verranno chiamate.
blubberdiblub,

354

Oltre ai metodi del dizionario (che mi piace molto, a proposito), puoi anche usare if- elif- elseper ottenere la funzionalità switch/ case/ default:

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

Questo ovviamente non è identico a switch / case: non si può fallire facilmente come lasciare la breakdichiarazione, ma si può avere un test più complicato. La sua formattazione è migliore di una serie di ifs nidificati , anche se funzionalmente è quello che è più vicino.


51
preferirei davvero questo, usa un costrutto di linguaggio standard e non genera un KeyError se non viene trovato alcun caso corrispondente
martyglaubitz,

7
Ho pensato al dizionario / getmodo, ma il modo standard è semplicemente più leggibile.
Martin Thoma,

2
@someuser, ma il fatto che possano "sovrapporsi" è una caratteristica. Devi solo assicurarti che l'ordine sia la priorità in cui dovrebbero verificarsi le partite. Per quanto riguarda x ripetuto: basta fare un x = the.other.thingprima. In genere, avresti un singolo if, multiple elif e un singolo altro, poiché è più facile da capire.
Matthew Schinckel,

7
Bello, il "Fall-through non usando elif" è un po 'confuso, però. Che ne dici di questo: dimentica di "fall through" e accettalo come due if/elif/else?
Alois Mahdal,

7
Vale anche la pena ricordare che, quando si usano cose del genere x in 'bc', tenere presente che lo "" in "bc"è True.
Lohmar ASHAR,

185

La mia ricetta preferita di Python per switch / case è:

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

Breve e semplice per scenari semplici.

Confronta con 11+ righe di codice C:

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

Puoi anche assegnare più variabili usando le tuple:

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

16
Trovo che questa sia una risposta più solida di quella accettata.
cerd

3
@some user: C richiede che il valore restituito sia dello stesso tipo per tutti i casi. Python no. Volevo evidenziare questa flessibilità di Python nel caso in cui qualcuno avesse una situazione che giustificasse tale utilizzo.
ChaimG

3
@some user: Personalmente, trovo {} .get (,) leggibile. Per una maggiore leggibilità per i principianti di Python potresti voler usare default = -1; result = choices.get(key, default).
ChaimG

4
confronta con 1 riga di c ++result=key=='a'?1:key==b?2:-1
Jasen del

4
@Jasen si può sostenere che si può fare in una sola riga di Python pure: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). ma è leggibile l'unica fodera?
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))

Uso:

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

test:

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

64
Questa non è una minaccia sicura. Se vengono colpiti più interruttori contemporaneamente, tutti gli interruttori assumono il valore dell'ultimo interruttore.
francescortiz,

48
Sebbene @francescortiz probabilmente significhi thread-safe, non è nemmeno sicuro dalle minacce. Minaccia i valori delle variabili!
Zizouz212,

7
Probabilmente il problema di sicurezza del thread potrebbe essere aggirato usando l'archiviazione locale del thread . Oppure potrebbe essere evitato del tutto restituendo un'istanza e usando quell'istanza per il confronto dei casi.
blubberdiblub,

6
@blubberdiblub Ma allora non è solo più efficiente utilizzare ifun'istruzione standard ?
wizzwizz4,

9
Anche questo non è sicuro se utilizzato in più funzioni. Nell'esempio dato, se il case(2)blocco ha chiamato un'altra funzione che utilizza switch (), quindi quando fa case(2, 3, 5, 7)etc per cercare il caso successivo da eseguire, utilizzerà il valore di switch impostato dall'altra funzione e non quello impostato dall'istruzione switch corrente .
user9876

52

La mia preferita è una ricetta davvero carina . Ti piacerà davvero. È il più vicino che ho visto per le affermazioni del caso switch, in particolare per quanto riguarda le funzionalità.

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

Ecco un esempio:

# 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
Vorrei sostituire for case in switch()con with switch() as case, ha più senso, dal momento che hanno bisogno di s per essere eseguito solo una volta.
Sci

4
@Skirmantas: Nota che withnon lo consente breakperò, quindi l'opzione fallthrough è tolta.
Jonas Schäfer,

5
Mi scuso per non aver fatto più sforzi per determinarlo da solo: una risposta simile sopra non è sicura per i thread. È questo?
David Winiecki,

1
@DavidWiniecki I componenti del codice mancanti da quanto sopra (e possibilmente il copyright di activestate) sembrano essere thread-safe.
Jasen,

un'altra versione di questo sarebbe qualcosa del genere 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
L'uso dei gestori di contesto è una buona soluzione creativa. Consiglierei di aggiungere un po 'di spiegazione e forse un link ad alcune informazioni sui Context Manager per dare a questo post un po' di contesto;)
Will

2
Non mi piacciono molto le catene / elif, ma questa è sia la più creativa che la più pratica di tutte le soluzioni che ho visto usando la sintassi esistente di Python.
itsbruce,

2
È davvero carino Un miglioramento suggerito è l'aggiunta di una valueproprietà (pubblica) alla classe Switch in modo da poter fare riferimento case.valueall'interno dell'istruzione.
Peter,

48

C'è un modello che ho imparato dal codice di Twisted Python.

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'

Puoi usarlo ogni volta che devi inviare un token ed eseguire un pezzo di codice esteso. In una macchina a stati avresti state_metodi e invio self.state. Questa opzione può essere estesa in modo chiaro ereditando dalla classe base e definendo i propri do_metodi. Spesso non avrai nemmeno do_metodi nella classe base.

Modifica: come viene utilizzato esattamente

In caso di SMTP riceverai HELOdal bonifico. Il codice pertinente (da twisted/mail/smtp.py, modificato per il nostro caso) è simile al seguente

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

Riceverai ' HELO foo.bar.com '(o potresti ottenere 'QUIT'o 'RCPT TO: foo'). Questo è tokenizzato in partsas ['HELO', 'foo.bar.com']. Viene preso il nome di ricerca del metodo effettivo parts[0].

(Viene anche chiamato il metodo originale state_COMMAND, perché utilizza lo stesso modello per implementare una macchina a stati, ovvero getattr(self, 'state_' + self.mode))


4
Non vedo il vantaggio di questo modello semplicemente chiamando i metodi direttamente: SMTP (). Do_HELO ('foo.bar.com') OK, può esserci un codice comune nel lookupMethod, ma poiché anche questo può essere sovrascritto da la sottoclasse non vedo cosa guadagni dall'indirizzamento indiretto.
Mr Shark,

1
Non sapresti quale metodo chiamare in anticipo, vale a dire "HELO" proviene da una variabile. ho aggiunto un esempio di utilizzo al post originale

Vorrei suggerire semplicemente: eval ('SMTP (). Do_' + comando) ('foo.bar.com')
jforberg

8
eval? sul serio? e invece di creare un'istanza di un metodo per chiamata, possiamo benissimo creare un'istanza una volta e utilizzarla in tutte le chiamate purché non abbia uno stato interno.
Mahesh,

1
IMO la vera chiave qui è l'invio tramite getattr per specificare una funzione da eseguire. Se i metodi fossero in un modulo, potresti ottenere getattr (locals (), func_name) per ottenerlo. La parte 'do_' è utile per sicurezza / errori, quindi è possibile chiamare solo funzioni con il prefisso. SMTP stesso chiama lookupMethod. Idealmente l'esterno non sa nulla di tutto ciò. Non ha molto senso fare SMTP (). LookupMethod (name) (data). Dal momento che il comando e i dati sono in una stringa e SMTP lo analizza, questo ha più senso. Infine, SMTP ha probabilmente un altro stato condiviso che giustifica il fatto che sia una classe.
ShawnFumo,

27

Diciamo che non vuoi solo restituire un valore, ma vuoi usare metodi che cambiano qualcosa su un oggetto. Utilizzando l'approccio indicato qui sarebbe:

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

Quello che succede qui è che Python valuta tutti i metodi nel dizionario. Quindi, anche se il tuo valore è 'a', l'oggetto verrà incrementato e decrementato di x.

Soluzione:

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

result = func(*args)

Quindi ottieni un elenco contenente una funzione e i suoi argomenti. In questo modo, vengono restituiti solo il puntatore a funzione e l'elenco degli argomenti, non valutati. 'risultato' quindi valuta la chiamata di funzione restituita.


23

Sto solo lasciando cadere i miei due centesimi qui. Il motivo per cui non esiste un'istruzione case / switch in Python è perché Python segue il principio di "C'è solo un modo giusto per fare qualcosa". Quindi, ovviamente, potresti trovare vari modi per ricreare la funzionalità switch / case, ma il modo Pythonic per realizzare questo è il costrutto if / elif. vale a dire

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

Ho appena sentito che PEP 8 meritava un cenno qui. Una delle cose belle di Python è la sua semplicità ed eleganza. Ciò è in gran parte derivato dai principi stabiliti nel PEP 8, tra cui "C'è solo un modo giusto per fare qualcosa"


6
Allora perché Python ha per i loop e mentre i loop? Tutto ciò che puoi fare con un ciclo for che puoi implementare con un ciclo while.
itsbruce,

1
Vero. Switch / case sono troppo spesso utilizzati dai programmatori principianti. Quello che vogliono veramente è il modello di strategia .
user228395

Python vorrebbe che fosse Clojure
TWR Cole il

1
@TWRCole Non credo, Python lo stava facendo per primo. Python esiste dal 1990 e Clojure dal 2007.
Taylor,

C'è solo un modo giusto per fare qualcosa. Python 2.7 o Python 3? Lol.
TWR Cole,

17

espandendo l'idea "dict as switch". se si desidera utilizzare un valore predefinito per lo switch:

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

14
Penso che sia più chiaro usare .get () sul dict con il default specificato. Preferisco lasciare Eccezioni per circostanze eccezionali, e taglia tre righe di codice e un livello di rientro senza essere oscuro.
Chris B.

10
Questa è una circostanza eccezionale. Può essere o meno una circostanza rara a seconda dell'utile, ma è sicuramente un'eccezione (ripiegare 'default') dalla regola (ottenere qualcosa da questo dict). In base alla progettazione, i programmi Python utilizzano le eccezioni al rilascio di un cappello. Detto questo, l'utilizzo getpotrebbe potenzialmente rendere il codice un po 'più bello.
Mike Graham,

16

Se si dispone di un blocco maiuscolo complicato, è possibile utilizzare una tabella di ricerca del dizionario delle funzioni ...

Se non l'hai mai fatto prima, è una buona idea entrare nel tuo debugger e vedere esattamente come il dizionario cerca ogni funzione.

NOTA: non utilizzare "()" all'interno della ricerca caso / dizionario o chiamerà ciascuna delle funzioni man mano che viene creato il blocco dizionario / caso. Ricordalo perché vuoi chiamare ciascuna funzione solo una volta usando una ricerca in stile hash.

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()

Mi piace la tua soluzione. Ma cosa succede se devo solo passare alcune variabili o oggetti?
Tedo Vrbanec,

Questo non funzionerà se il metodo prevede parametri.
Kulasangar,

16

Se stai cercando istruzioni extra, come "switch", ho creato un modulo Python che estende Python. Si chiama ESPY come "Enhanced Structure for Python" ed è disponibile per Python 2.xe Python 3.x.

Ad esempio, in questo caso, un'istruzione switch potrebbe essere eseguita dal seguente codice:

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

che può essere usato in questo modo:

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

quindi espy lo traduce in Python come:

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

Molto bello, ma qual è il punto while True:in cima al codice Python generato? Inevitabilmente colpirà il breakfondo del codice Python generato, quindi mi sembra che sia while True:e che breakpossano essere rimossi. Inoltre, ESPY è abbastanza intelligente da modificare il nome contse l'utente utilizza lo stesso nome nel proprio codice? In ogni caso, voglio usare Python alla vaniglia, quindi non lo userò, ma è comunque bello. +1 per pura freddezza.
ArtOfWarfare il

@ArtOfWarfare La ragione per la while True:e breaks è quello di consentire, ma non richiedono autunno-through.
Solomon Ucko,

Questo modulo è ancora disponibile?
Solomon Ucko

15

Ho scoperto che una struttura switch comune:

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

può essere espresso in Python come segue:

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

o formattato in modo più chiaro:

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

Invece di essere un'istruzione, la versione di Python è un'espressione, che valuta un valore.


Inoltre, invece di ... parametri ... e p1 (x) come circa parameterep1==parameter
Bob Stein

@ BobStein-Visibone hi, ecco un esempio che viene eseguito in mia sessione pitone: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Quando in seguito ho chiamato f(2), ho ricevuto 'c'; f(1), 'b'; e f(0), 'a'. Per quanto riguarda p1 (x), indica un predicato; purché ritorni Trueo False, non importa che sia una chiamata di funzione o un'espressione, va bene.
Leo,

@ BobStein-VisiBone Sì, hai ragione! Grazie :) Perché l'espressione a più righe funzioni, le parentesi devono essere posizionate, come nel tuo suggerimento, o come nel mio esempio modificato.
leo

Eccellente. Ora eliminerò tutti i miei commenti sulle parentesi.
Bob Stein,

15

La maggior parte delle risposte qui sono piuttosto vecchie, e specialmente quelle accettate, quindi sembra che valga la pena aggiornarle.

Innanzitutto, le FAQ ufficiali di Python coprono questo aspetto e raccomandano la elifcatena per casi semplici e casi dictpiù grandi o più complessi. Suggerisce inoltre una serie di visit_metodi (uno stile utilizzato da molti framework di server) per alcuni casi:

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

Le FAQ menzionano anche PEP 275 , che è stato scritto per ottenere una decisione una tantum ufficiale sull'aggiunta di istruzioni switch C-style. Ma quel PEP è stato effettivamente rinviato a Python 3 ed è stato ufficialmente respinto solo come proposta separata, PEP 3103 . La risposta è stata ovviamente no, ma i due PEP hanno collegamenti a informazioni aggiuntive se siete interessati ai motivi o alla cronologia.


Una cosa che è emersa più volte (e può essere vista in PEP 275, anche se è stata ritagliata come una vera raccomandazione) è che se sei davvero infastidito dall'avere 8 righe di codice per gestire 4 casi, rispetto al 6 righe che avresti in C o Bash, puoi sempre scrivere questo:

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

Questo non è esattamente incoraggiato da PEP 8, ma è leggibile e non troppo unidiomatico.


Nel corso di oltre un decennio da quando PEP 3103 è stato respinto, il problema delle dichiarazioni di casi in stile C, o persino della versione leggermente più potente di Go, è stato considerato morto; ogni volta che qualcuno lo solleva su python-ideas o -dev, viene riferito alla vecchia decisione.

Tuttavia, l'idea di una perfetta corrispondenza del modello in stile ML nasce ogni pochi anni, soprattutto da quando sono stati adottati linguaggi come Swift e Rust. Il problema è che è difficile sfruttare al meglio la corrispondenza dei pattern senza tipi di dati algebrici. Mentre Guido è stato simpatico all'idea, nessuno ha escogitato una proposta che si adatta molto bene a Python. (Puoi leggere il mio strawman del 2014 per un esempio.) Questo potrebbe cambiare con dataclassin 3.7 e alcune sporadiche proposte per un enumtipo di somma più potente da gestire, o con varie proposte per diversi tipi di associazioni locali-dichiarazione (come PEP 3150 , o il serie di proposte attualmente in discussione in -ideas). Ma finora no.

Occasionalmente ci sono anche proposte per l'abbinamento in stile Perl 6, che è fondamentalmente un miscuglio di tutto, dal elifregex al cambio di tipo a singolo invio.


15

Soluzione per eseguire funzioni:

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

dove foo1 (), foo2 (), foo3 () e default () sono funzioni


1
Sì, ad esempio se l'opzione variabile == "case2" il risultato = foo2 ()
Alejandro Quintanar,

e così e così via.
Alejandro Quintanar,

Sì, capisco lo scopo. Ma la mia preoccupazione è che se desideri solo foo2(), i foo1(), foo3()e le default()funzioni sono tutte anche andando a correre, che significa le cose potrebbero richiedere molto tempo
Brian Underwood

1
ometti il ​​() all'interno del dizionario. usare get(option)(). problema risolto.
orario

1
Eccellente l'uso di () è una soluzione a griglia, ho fatto un tentativo
Alejandro Quintanar

13

Non ho trovato la risposta semplice che cercavo da nessuna parte nella ricerca di Google. Ma l'ho capito comunque. È davvero abbastanza semplice. Ha deciso di pubblicarlo e forse prevenire qualche graffio in meno sulla testa di qualcun altro. La chiave è semplicemente "in" e tuple. Ecco il comportamento dell'istruzione switch con fall-through, incluso il fall-through RANDOM.

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)

Fornisce:

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.

Dove si trova esattamente qui?
Jonas Schäfer,

Oops! C'è caduta lì, ma non sto più contribuendo allo Stack Overflow. Non mi piacciono affatto. Mi piacciono i contributi di altri, ma non StackOverflow. Se si utilizza fall fall per FUNZIONALITÀ, si desidera CATTURARE determinate condizioni in un'istruzione case in uno switch (un catch all), fino a quando non si raggiunge un'istruzione break in uno switch.
JD Graham,

2
Qui entrambi i valori "Cane" e "Gatto" CADONO ATTRAVERSO e sono gestiti dalla STESSA funzionalità, che è definita come "a quattro zampe". È un ABSTRACT equivalente a fallire e diversi valori gestiti dall'istruzione case SAME in cui si verifica un'interruzione.
JD Graham,

@JDGraham Penso che Jonas intendesse un altro aspetto del fallthrough, che accade quando il programmatore ogni tanto dimentica di scrivere breakalla fine del codice per a case. Ma penso che non abbiamo bisogno di tale "caduta" :)
Mikhail Batcer,

12

Le soluzioni che utilizzo:

Una combinazione di 2 delle soluzioni pubblicate qui, che è relativamente facile da leggere e supporta le impostazioni predefinite.

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

dove

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

cerca "lambda x: x - 2"nel dict e lo usa conx=23

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

non lo trova nel dict e utilizza l'impostazione predefinita "lambda x: x - 22"con 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

Considera di includere una breve descrizione del tuo codice e di come risolve la domanda postata
Henry Woody,

Ok, ho aggiunto un commento per questo ora.
Vikhyat Agarwal,

8

Mi è piaciuta la risposta di Mark Bies

Poiché la xvariabile deve essere utilizzata due volte, ho modificato le funzioni lambda in senza parametri.

Devo correre con 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'

Modifica: ho notato che posso usare il Nonetipo con i dizionari. Quindi questo emulerebbeswitch ; case else


Il caso Nessuno non emula semplicemente result[None]()?
Bob Stein,

Si, esattamente. Voglio direresult = {'a': 100, None:5000}; result[None]
guneysus l'

4
Basta controllare che nessuno stia pensando che None:si comporti come default:.
Bob Stein,

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

Breve e facile da leggere, ha un valore predefinito e supporta espressioni sia in condizioni che in valori di ritorno.

Tuttavia, è meno efficiente della soluzione con un dizionario. Ad esempio, Python deve esaminare tutte le condizioni prima di restituire il valore predefinito.


7

puoi usare un dict spedito:

#!/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()

Produzione:

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

6

Semplice, non testato; ogni condizione viene valutata in modo indipendente: non esiste un fall-through, ma vengono valutati tutti i casi (sebbene l'espressione da attivare venga valutata solo una volta), a meno che non vi sia un'istruzione break. Per esempio,

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. ')

stampe Was 1. Was 1 or 2. Was something. (Dannazione! Perché non posso avere spazi vuoti finali in blocchi di codice inline?) se expressionvaluta 1, Was 2.se expressionvaluta 2o Was something.se expressionvaluta qualcos'altro.


1
Bene, la caduta attraverso funziona, ma solo per andare a do_default.
Syockit,

5

Definizione:

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

ti permette di usare una sintassi abbastanza semplice, con i casi raggruppati in una mappa:

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

Continuavo a cercare di ridefinire l'interruttore in un modo che mi permettesse di liberarmi del "lambda:", ma mi sono arreso. Modifica della definizione:

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

Mi ha permesso di mappare più casi sullo stesso codice e di fornire un'opzione predefinita:

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")
    })

Ogni caso replicato deve essere nel proprio dizionario; switch () consolida i dizionari prima di cercare il valore. È ancora più brutto di quanto mi piacerebbe, ma ha l'efficienza di base dell'uso di una ricerca con hash sull'espressione, piuttosto che un ciclo attraverso tutte le chiavi.


5

Penso che il modo migliore sia usare gli idiomi del linguaggio python per mantenere testabile il tuo codice . Come mostrato nelle risposte precedenti, utilizzo i dizionari per sfruttare le strutture e il linguaggio di Python e mantenere il codice "case" isolato in diversi metodi. Di seguito è presente una classe, ma è possibile utilizzare direttamente un modulo, globi e funzioni. La classe ha metodi che possono essere testati con isolamento . A seconda delle tue esigenze, puoi giocare anche con metodi e attributi statici.

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)

È possibile sfruttare questo metodo usando anche le classi come chiavi di "__choice_table". In questo modo puoi evitare l'abuso di isinstance e mantenere tutto pulito e testabile.

Supponendo che tu debba elaborare molti messaggi o pacchetti dalla rete o dal tuo MQ. Ogni pacchetto ha la sua struttura e il suo codice di gestione (in modo generico). Con il codice sopra è possibile fare qualcosa del genere:

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)

Quindi la complessità non si diffonde nel flusso del codice ma viene resa nella struttura del codice .


Davvero brutto ... la custodia è così pulita durante la lettura. Non riesco a capire perché non sia implementato in Python.
jmcollin92,

@AndyClifton: mi dispiace ... un esempio? Pensa a ogni volta che devi disporre di più codici di ramificazione decisionale e puoi applicare questo metodo.
J_Zar

@ jmcollin92: l'istruzione switch è comoda, sono d'accordo. Tuttavia, il programmatore tende a scrivere dichiarazioni e codici molto lunghi che non sono riutilizzabili. Il modo in cui ho descritto è più pulito da testare e più riutilizzabile, IMHO.
J_Zar

@J_Zar: re. La mia richiesta per un esempio: sì, ho capito, ma faccio fatica a inserirla nel contesto di un pezzo di codice più ampio. Potresti mostrare come potrei usarlo in una situazione del mondo reale?
Andy Clifton,

1
@AndyClifton: mi dispiace, sono in ritardo ma ho pubblicato un caso esemplare.
J_Zar

5

Espandendo la risposta di Greg Hewgill - Possiamo incapsulare la soluzione del dizionario usando un decoratore:

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

Questo può quindi essere usato con il @case-decorator

@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

La buona notizia è che questo è già stato fatto nel modulo NeoPySwitch . Installa semplicemente usando pip:

pip install NeoPySwitch

5

Una soluzione che tendo a utilizzare che utilizza anche dizionari è:

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)()

Questo ha il vantaggio di non provare a valutare le funzioni ogni volta e devi solo assicurarti che la funzione esterna ottenga tutte le informazioni di cui le funzioni interne hanno bisogno.


5

Finora ci sono state molte risposte che hanno detto: "Non abbiamo un interruttore in Python, fallo in questo modo". Tuttavia, vorrei sottolineare che la stessa istruzione switch è un costrutto facilmente abusato che può e dovrebbe essere evitato nella maggior parte dei casi perché promuove la programmazione pigra. Caso in questione:

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

Ora, si potrebbe fare questo con un interruttore-statement (se Python offerto uno), ma si sarebbe sprecare il vostro tempo, perché ci sono metodi che fanno questo bene. O forse, hai qualcosa di meno ovvio:

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

Tuttavia, questo tipo di operazione può e deve essere gestita con un dizionario perché sarà più veloce, meno complesso, meno soggetto a errori e più compatto.

E la stragrande maggioranza dei "casi d'uso" per le istruzioni switch rientrerà in uno di questi due casi; ci sono pochissime ragioni per usarne uno se hai riflettuto a fondo sul tuo problema.

Quindi, piuttosto che chiedere "come faccio a cambiare in Python?", Forse dovremmo chiedere "perché voglio passare a Python?" perché questa è spesso la domanda più interessante e spesso esporrà difetti nella progettazione di qualsiasi cosa tu stia costruendo.

Ora, ciò non vuol dire che nemmeno gli switch dovrebbero essere usati. Le macchine a stati, i lexer, i parser e gli automi li usano tutti in una certa misura e, in generale, quando si parte da un input simmetrico e si passa a un output asimmetrico, possono essere utili; devi solo assicurarti di non usare l'interruttore come un martello perché vedi un mucchio di chiodi nel tuo codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.