Zastąpienia instrukcji switch w Pythonie?


1718

Chcę napisać funkcję w języku Python, która zwraca różne stałe wartości na podstawie wartości indeksu wejściowego.

W innych językach użyłbym instrukcji switchlub case, ale wydaje się, że Python nie ma switchinstrukcji. Jakie są zalecane rozwiązania Python w tym scenariuszu?


77
Powiązany PEP, autor: Guido: PEP 3103
chb

28
@chb W tym PEP Guido nie wspomina, że ​​łańcuchy / elif są również klasycznym źródłem błędów. To bardzo delikatna konstrukcja.
itsbruce

15
Brakuje tutaj wszystkich rozwiązań, aby wykryć zduplikowane wartości wielkości liter . Zasadniczo jest to zasada szybkiej awarii, która może być ważniejszą stratą niż wydajność lub funkcja awaryjna.
Bob Stein

6
switchjest w rzeczywistości bardziej „wszechstronny” niż coś zwracającego różne stałe wartości na podstawie wartości indeksu wejściowego. Pozwala na wykonanie różnych fragmentów kodu. W rzeczywistości nie musi nawet zwracać wartości. Zastanawiam się, czy niektóre z odpowiedzi tutaj są dobrym zamiennikiem switchinstrukcji ogólnej , czy tylko w przypadku zwracania wartości bez możliwości wykonania ogólnych fragmentów kodu.
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Kruchy konstrukt, podobnie jak pętla while jest kruchym konstruktem, jeśli spróbujesz użyć go do robienia tego, co dla ... za ... Czy zamierzasz nazywać programistów słabymi do używania pętli? Podczas gdy pętle są wszystkim, czego naprawdę potrzebują. Ale w przypadku pętli pokaż wyraźne zamiary, zapisz niepotrzebny szablon i daj możliwość tworzenia potężnych abstrakcji.
itsbruce

Odpowiedzi:


1486

Możesz użyć słownika:

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

100
Co się stanie, jeśli x nie zostanie znaleziony?
Nick

46
@nick: możesz użyć defaultdict
Eli Bendersky

385
Polecam umieszczenie dykta poza funkcją, jeśli wydajność stanowi problem, więc nie odbudowuje dykta przy każdym wywołaniu funkcji
Claudiu

56
@EliBendersky, Korzystanie z getmetody byłoby prawdopodobnie bardziej normalne niż używanie collections.defaultdictw tym przypadku.
Mike Graham

27
@Nick, Zgłaszany jest wyjątek - zrób to, }.get(x, default)jeśli powinna być domyślna. (Uwaga: jest to o wiele ładniejsze niż to, co się stanie, jeśli nie wyłączysz domyślnej instrukcji zamiany!)
Mike Graham

1375

Jeśli chcesz mieć wartości domyślne, możesz użyć get(key[, default])metody słownikowej :

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

11
Co jeśli „a” i „b” pasują do 1, a „c” i „d” pasują do 2?
John Mee,

13
@JM: Cóż, oczywiście wyszukiwania słownikowe nie obsługują przewrotów. Możesz zrobić podwójne wyszukiwanie słownika. Tj. „A” i „b” wskazują na odpowiedź 1 oraz „c” i „d” wskazują na odpowiedź 2, które są zawarte w drugim słowniku.
Nick

3
lepiej jest przekazać wartość domyślną
HaTiMSuM

Występują problemy z tym podejściem, po pierwsze za każdym razem, gdy zadzwonisz, aby utworzyć ponownie dyktando, a po drugie, jeśli masz bardziej złożoną wartość, możesz uzyskać wyjątki np. jeśli x jest krotką i chcemy zrobić coś takiego: x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) To podniesie IndexError
Idan Haim Shalom

2
@Idan: Pytanie dotyczyło replikacji przełącznika. Jestem pewien, że mógłbym również złamać ten kod, gdybym spróbował wprowadzić nieparzyste wartości. Tak, odtworzy się ponownie, ale można to łatwo naprawić.
Nick

394

Zawsze lubiłem to robić w ten sposób

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

Stąd


16
Świetna metoda w połączeniu z get () do obsługi domyślnych jest również moim najlepszym wyborem
drAlberT

27
w tym przypadku może nie jest dobrym pomysłem użycie lambda, ponieważ lambda jest wywoływana za każdym razem, gdy budowany jest słownik.
Asher

13
Niestety są to najbliżsi ludzie. Metody, które wykorzystują .get()(podobnie jak obecne najwyższe odpowiedzi) będą musiały z niecierpliwością oceniać wszystkie możliwości przed wysłaniem, a zatem nie tylko są (nie tylko bardzo, ale) niezwykle nieefektywne i nie mogą mieć skutków ubocznych; ta odpowiedź omija ten problem, ale jest bardziej szczegółowa. Użyłbym tylko if / elif / else, a nawet te zajmują tyle samo czasu, co pisanie „case”.
ninjagecko

13
czy to nie ocenia wszystkich funkcji / lambdas za każdym razem we wszystkich przypadkach, nawet jeśli zwraca tylko jeden z wyników?
slf

23
@ slf Nie, kiedy przepływ kontrolny osiągnie ten fragment kodu, zbuduje 3 funkcje (za pomocą 3 lambdas), a następnie zbuduje słownik z tymi 3 funkcjami jako wartościami, ale pozostaną one niepotrzebne ( ocena jest nieco niejednoznaczna w ten kontekst) na początku. Następnie słownik jest indeksowany przez [value], co zwróci tylko jedną z 3 funkcji (zakładając, że valuejest to jeden z 3 klawiszy). Funkcja nie została jeszcze wywołana w tym momencie. Następnie (x)wywołuje właśnie zwróconą funkcję z xargumentem as (a wynik przechodzi do result). Pozostałe 2 funkcje nie zostaną wywołane.
blubberdiblub

353

Oprócz metod słownikowych (które naprawdę lubię BTW) możesz także użyć if- elif- w elsecelu uzyskania funkcji 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

To oczywiście nie jest identyczne z przełącznikiem / skrzynką - nie możesz tak łatwo przejść przez to, co pomijanie breakinstrukcji, ale możesz mieć bardziej skomplikowany test. Jego formatowanie jest ładniejsze niż seria zagnieżdżonych ifs, nawet jeśli funkcjonalnie jest to do tego bliżej.


51
naprawdę wolałbym to, używa standardowej konstrukcji języka i nie wyrzuca KeyError, jeśli nie zostanie znaleziony pasujący przypadek
martyglaubitz

7
Myślałem o słowniku / getsposobie, ale standardowy sposób jest po prostu bardziej czytelny.
Martin Thoma,

2
@someuser, ale fakt, że mogą się one nakładać, jest cechą. Po prostu upewnij się, że kolejność jest priorytetem, w którym powinny wystąpić dopasowania. Co do powtarzanego x: po prostu zrób x = the.other.thingwcześniej. Zwykle miałbyś jeden, wiele elifów i jeden inny, co jest łatwiejsze do zrozumienia.
Matthew Schinckel

7
Fajnie, jednak „Upadek przez nieużywanie elif” jest nieco mylący. A co z tym: zapomnieć o „wpadnięciu” i zaakceptować to jako dwoje if/elif/else?
Alois Mahdal

7
Warto również wspomnieć, używając takich rzeczy x in 'bc', należy pamiętać, że tak "" in "bc"jest True.
Lohmar ASHAR

185

Mój ulubiony przepis Pythona na przełącznik / skrzynkę to:

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

Krótkie i proste dla prostych scenariuszy.

Porównaj z ponad 11 liniami kodu C:

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

Możesz nawet przypisać wiele zmiennych za pomocą krotek:

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

16
Uważam, że jest to bardziej solidna odpowiedź niż zaakceptowana.
cerd

3
@some user: C wymaga, aby zwracana wartość była tego samego typu dla wszystkich przypadków. Python nie. Chciałem podkreślić tę elastyczność Pythona na wypadek, gdyby ktoś miał sytuację uzasadniającą takie użycie.
ChaimG

3
@ niektórzy użytkownicy: osobiście uważam, że {} .get (,) jest czytelny. Dla dodatkowej czytelności dla początkujących w Pythonie możesz chcieć użyć default = -1; result = choices.get(key, default).
ChaimG

4
porównaj z 1 linią c ++result=key=='a'?1:key==b?2:-1
Jasen

4
@Jasen można argumentować, że można to zrobić w jednej linii z Pythonem, a także: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). ale czy jedna linijka jest czytelna?
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))

Stosowanie:

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

Testy:

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

64
Nie jest to bezpieczne. Jeśli kilka przełączników zostanie trafionych jednocześnie, wszystkie przełączniki przyjmują wartość ostatniego przełącznika.
francescortiz

48
Chociaż @francescortiz prawdopodobnie oznacza bezpieczny wątek, nie jest również bezpieczny. Zagraża to wartości zmiennych!
Zizouz212

7
Problem bezpieczeństwa wątków można prawdopodobnie obejść, używając magazynu lokalnego wątków . Lub można tego całkowicie uniknąć, zwracając instancję i wykorzystując ją do porównań spraw.
blubberdiblub

6
@blubberdiblub Ale czy nie jest po prostu bardziej wydajne stosowanie standardowej ifinstrukcji?
wizzwizz4

9
Nie jest to również bezpieczne, jeśli jest używane w wielu funkcjach. W podanym przykładzie, jeśli case(2)blok wywołuje inną funkcję, która korzysta z switch (), to podczas wykonywania case(2, 3, 5, 7)itp. W celu wyszukania następnego przypadku do wykonania użyje wartości przełączania ustawionej przez inną funkcję, a nie tej ustawionej przez bieżącą instrukcję przełączania .
user9876,

52

Mój ulubiony to naprawdę fajny przepis . Naprawdę ci się spodoba. Jest to najbliższy, jaki widziałem rzeczywiste instrukcje przypadków przełączania, szczególnie w funkcjach.

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

Oto przykład:

# 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
Chciałbym zastąpić for case in switch()z with switch() as case, ma większy sens, ponieważ trzeba s uruchomić tylko raz.
Ski

4
@ Skirmantas: Zauważ, że to na withto nie pozwala break, więc opcja przewrotu jest usuwana.
Jonas Schäfer

5
Przepraszamy za to, że nie włożyłem więcej wysiłku w samodzielne ustalenie tego: podobna odpowiedź powyżej nie jest bezpieczna. Czy to jest?
David Winiecki

1
@DavidWiniecki Brakujące w powyższym składniku kodu (i prawdopodobnie prawa autorskie przez activestate) są bezpieczne dla wątków.
Jasen

czy inna wersja tego byłaby czymś takim 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
Korzystanie z menedżerów kontekstu to dobre kreatywne rozwiązanie. Polecam dodać trochę wyjaśnień i może link do niektórych informacji na temat menedżerów kontekstu, aby nadać temu postowi trochę, no cóż, kontekst;)
Będzie

2
Nie podoba mi się, jeśli łańcuchy / elif są dużo, ale jest to zarówno najbardziej kreatywne, jak i najbardziej praktyczne ze wszystkich rozwiązań, jakie widziałem przy użyciu istniejącej składni Pythona.
itsbruce

2
To jest naprawdę miłe. Jednym z sugerowanych ulepszeń jest dodanie (publicznej) valuewłaściwości do klasy Switch, aby można było odwoływać się case.valuedo instrukcji.
Peter

48

Jest pewien wzór, którego nauczyłem się z kodu 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'

Możesz go użyć w dowolnym momencie, gdy potrzebujesz wysłać token i wykonać rozszerzony fragment kodu. W maszynie stanów masz state_metody i wysyłamy dalej self.state. Przełącznik można rozszerzyć, dziedzicząc po klasie podstawowej i definiując własne do_metody. Często nawet nie masz do_metod w klasie bazowej.

Edycja: jak dokładnie to jest używane

W przypadku SMTP otrzymasz HELOz drutu. Odpowiedni kod (z twisted/mail/smtp.py, zmodyfikowany w naszym przypadku) wygląda następująco

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

Otrzymasz ' HELO foo.bar.com '(lub możesz dostać 'QUIT'lub 'RCPT TO: foo'). Jest to symbolizowane partsjako ['HELO', 'foo.bar.com']. Rzeczywista nazwa wyszukiwania metody pochodzi z parts[0].

(Oryginalna metoda jest również wywoływana state_COMMAND, ponieważ używa tego samego wzorca do implementacji automatu stanów, tj. getattr(self, 'state_' + self.mode))


4
Nie widzę korzyści z tego wzorca w porównaniu do bezpośredniego wywoływania metod: SMTP (). Do_HELO ('foo.bar.com') OK, w lookupMethod może być wspólny kod, ale ponieważ można go również zastąpić podklasa Nie widzę, co zyskujesz z pośrednictwa.
Pan Shark

1
Nie wiedziałbyś, jaką metodę zadzwonić wcześniej, to znaczy, że „HELO” pochodzi od zmiennej. dodałem przykład użycia do oryginalnego postu

Czy mogę zasugerować po prostu: eval ('SMTP (). Do_' + polecenie) ('foo.bar.com')
jforberg

8
eval? poważnie? i zamiast tworzenia jednej metody na każde połączenie, możemy bardzo dobrze utworzyć raz i użyć go we wszystkich połączeniach, pod warunkiem, że nie ma on stanu wewnętrznego.
Mahesh,

1
IMO prawdziwym kluczem tutaj jest wysyłanie za pomocą getattr w celu określenia funkcji do uruchomienia. Gdyby metody były w module, można by to zrobić za pomocą getattr (locals (), func_name). Część „do_” jest dobra dla bezpieczeństwa / błędów, więc można wywoływać tylko obiekty z prefiksem. Sam SMTP wywołuje metodę lookupMethod. Idealnie na zewnątrz nic o tym nie wiadomo. Naprawdę nie ma sensu wykonywać SMTP (). LookupMethod (name) (data). Ponieważ polecenie i dane są w jednym ciągu, a SMTP je analizuje, ma to większy sens. Wreszcie, SMTP prawdopodobnie ma inny stan współdzielenia, co uzasadnia, że ​​jest klasą.
ShawnFumo

27

Powiedzmy, że nie chcesz po prostu zwracać wartości, ale chcesz użyć metod, które zmieniają coś na obiekcie. Przy zastosowaniu podanego tutaj podejścia byłoby:

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

To, co się tutaj dzieje, polega na tym, że Python ocenia wszystkie metody w słowniku. Więc nawet jeśli twoja wartość to „a”, obiekt zostanie zwiększony i zmniejszony o x.

Rozwiązanie:

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

result = func(*args)

Otrzymujesz więc listę zawierającą funkcję i jej argumenty. W ten sposób zwracany jest tylko wskaźnik funkcji i lista argumentów, a nie wartościowane. „wynik” następnie ocenia zwrócone wywołanie funkcji.


23

Po prostu wrzucę tutaj moje dwa centy. Powodem, dla którego nie ma instrukcji case / switch w Pythonie, jest to, że Python działa zgodnie z zasadą „Istnieje tylko jeden właściwy sposób na zrobienie czegoś”. Oczywiście można wymyślić różne sposoby odtwarzania funkcji przełącznika / wielkości liter, ale pythonicznym sposobem osiągnięcia tego jest konstrukcja if / elif. to znaczy

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

Po prostu czułem, że PEP 8 zasługuje na skinienie głową. Jedną z pięknych rzeczy w Pythonie jest jego prostota i elegancja. Wynika to w dużej mierze z zasad określonych w PEP 8, w tym „Istnieje tylko jeden właściwy sposób, aby coś zrobić”


6
Dlaczego więc Python ma pętle while i pętle while? Wszystko, co możesz zrobić z pętlą for, możesz zaimplementować za pomocą pętli while.
itsbruce

1
Prawdziwe. Switch / case są zbyt często wykorzystywane przez początkujących programistów. To, czego naprawdę chcą, to wzór strategii .
user228395,

Wygląda na to, że Python żałuje, że to Clojure
TWR Cole

1
@TWRCole Nie sądzę, Python robił to pierwszy. Python istnieje od 1990 roku, a Clojure od 2007 roku.
Taylor

Jest tylko jeden właściwy sposób, aby coś zrobić. Python 2.7 lub Python 3? Lol.
TWR Cole

17

rozwinięcie idei „dict as switch”. jeśli chcesz użyć domyślnej wartości dla przełącznika:

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

14
Myślę, że łatwiej jest używać .get () w dykcie z podaną wartością domyślną. Wolę pozostawić wyjątki w wyjątkowych okolicznościach, co ogranicza trzy wiersze kodu i poziom wcięcia, nie pozostając niezrozumiałym.
Chris B.

10
To jest wyjątkowa okoliczność. Może to być rzadka okoliczność, w zależności od użyteczności, ale zdecydowanie jest to wyjątek (cofanie się 'default') od reguły (uzyskaj coś z tego słownika). Z założenia programy w języku Python używają wyjątków od razu. Biorąc to pod uwagę, użycie getmoże potencjalnie uczynić kod nieco ładniejszym.
Mike Graham

16

Jeśli masz skomplikowany blok spraw, możesz rozważyć użycie tabeli wyszukiwania słownika funkcji ...

Jeśli nie zrobiłeś tego wcześniej, dobrze jest wejść do debuggera i zobaczyć dokładnie, jak słownik wyszukuje każdą funkcję.

Uwaga: nie używać „()” wewnątrz obudowy / słownik odnośnika albo zadzwoni do każdego ze swoich funkcji jako blok słownik / sprawa jest tworzony. Pamiętaj o tym, ponieważ chcesz wywołać każdą funkcję tylko raz, używając wyszukiwania w stylu skrótu.

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

Podoba mi się twoje rozwiązanie. Ale co jeśli będę musiał przekazać jakieś zmienne lub obiekty?
Tedo Vrbanec,

To nie zadziała, jeśli metoda oczekuje parametrów.
Kulasangar

16

Jeśli szukasz dodatkowej instrukcji, jako „switch”, zbudowałem moduł Pythona, który rozszerza Pythona. Nazywa się ESPY jako „Enhanced Structure for Python” i jest dostępny zarówno dla Python 2.x, jak i Python 3.x.

Na przykład w tym przypadku instrukcja switch może zostać wykonana za pomocą następującego kodu:

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

które mogą być użyte w ten sposób:

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

więc espy przetłumaczy to w Pythonie jako:

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

Bardzo fajnie, ale jaki jest sens while True:generowania kodu Pythona? To będzie nieuchronnie hit breakna dole wygenerowanego kodu Pythona, więc wydaje mi się, że zarówno while True:i breakmoże zostać usunięta. Ponadto, czy ESPY jest wystarczająco inteligentny, aby zmienić nazwęcont jeśli użytkownik używa tej samej nazwy we własnym kodzie? W każdym razie chcę używać waniliowego Pythona, więc nie będę tego używał, ale mimo wszystko jest fajny. +1 za czysty chłód.
ArtOfWarfare

@ArtOfWarfare Powodem while True:i breakjest umożliwienie, ale nie wymaganie przewrotu .
Solomon Ucko

Czy ten moduł jest nadal dostępny?
Solomon Ucko

15

Odkryłem, że wspólna struktura przełączników:

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

można wyrazić w Pythonie w następujący sposób:

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

lub sformatowane w jaśniejszy sposób:

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

Zamiast być instrukcją, wersja Pythona jest wyrażeniem, którego wynikiem jest wartość.


Również zamiast ... parametru ... i p1 (x) co powiesz naparameterp1==parameter
Bob Stein

@ BobStein-VisiBone cześć, tutaj jest przykładem, który działa w moim sesji Pythona: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Kiedy później zadzwoniłem f(2), dostałem 'c'; f(1), 'b'; i f(0), 'a'. Co do p1 (x), oznacza predykat; tak długo, jak zwraca Truelub False, niezależnie od tego, czy jest to wywołanie funkcji, czy wyrażenie, jest w porządku.
leo

@ BobStein-VisiBone Tak, masz rację! Dziękuję :) Aby wyrażenie wieloliniowe działało, nawiasy powinny być umieszczone, tak jak w twojej sugestii, lub jak w moim zmodyfikowanym przykładzie.
leo

Świetny. Teraz usunę wszystkie moje komentarze na temat parens.
Bob Stein

15

Większość odpowiedzi tutaj jest dość stara, a zwłaszcza te zaakceptowane, więc warto je zaktualizować.

Po pierwsze, oficjalne FAQ Pythona obejmuje to i zaleca elifłańcuch dla prostych przypadków i dictdla większych lub bardziej złożonych przypadków. Sugeruje również zestaw visit_metod (styl używany przez wiele frameworków serwerowych) w niektórych przypadkach:

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

FAQ również wspomina PEP 275 , który został napisany, aby uzyskać oficjalną decyzję raz na zawsze o dodaniu instrukcji przełączania w stylu C. Ale ten PEP został odroczony do Pythona 3 i został oficjalnie odrzucony jako osobna propozycja, PEP 3103 . Odpowiedź brzmiała oczywiście nie - ale dwa PEP mają linki do dodatkowych informacji, jeśli interesują cię powody lub historia.


Jedną z rzeczy, które pojawiały się wiele razy (i można to zobaczyć w PEP 275, nawet jeśli zostało to wycięte jako rzeczywista rekomendacja) jest to, że jeśli naprawdę przeszkadza ci posiadanie 8 linii kodu do obsługi 4 przypadków, w porównaniu z 6 linie, które miałbyś w C lub Bash, zawsze możesz napisać to:

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

PEP 8 nie zachęca do tego, ale jest czytelny i niezbyt jednoznaczny.


Przez ponad dekadę od odrzucenia PEP 3103 kwestia instrukcji typu C lub nawet nieco bardziej rozbudowana wersja Go została uznana za martwą; ilekroć ktoś porusza to w python-pomysłach lub -dev, odnosi się do starej decyzji.

Jednak idea pełnego dopasowania wzorców w stylu ML pojawia się co kilka lat, zwłaszcza że takie języki, jak Swift i Rust ją przyjęły. Problem polega na tym, że trudno jest w dużym stopniu wykorzystać dopasowanie wzorca bez algebraicznych typów danych. Chociaż Guido był przychylny temu pomysłowi, nikt nie wpadł na propozycję, która bardzo dobrze pasuje do Pythona. (Możesz przeczytać mój strawman z 2014 r .). Może to ulec zmianie dataclassw wersji 3.7 i sporadycznych propozycjach dotyczących bardziej wydajnej enumobsługi typów sum lub różnych propozycji dla różnych rodzajów powiązań lokalnych instrukcji (takich jak PEP 3150 lub zestaw propozycji obecnie dyskutowanych na żywo). Ale jak dotąd tak nie było.

Od czasu do czasu pojawiają się również propozycje dopasowania w stylu Perla 6, które jest w zasadzie pomieszaniem wszystkiego, od elifwyrażeń regularnych po przełączanie typów pojedynczej wysyłki.


15

Rozwiązanie do uruchamiania funkcji:

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

gdzie foo1 (), foo2 (), foo3 () i default () są funkcjami


1
Tak, na przykład jeśli Twoja zmienna opcja == "case2" twój wynik = foo2 ()
Alejandro Quintanar

i tak dalej.
Alejandro Quintanar

Tak, rozumiem cel. Ale moim problemem jest to, że jeśli tylko chcesz foo2(), to foo1(), foo3()i default()funkcje są również zamiar uruchomić, czyli rzeczy może trwać długo
Brian Underwood

1
pomiń () w słowniku. użyć get(option)(). problem rozwiązany.
timbb

1
Doskonałe użycie () to kratowe rozwiązanie, postanowiłem przetestować go gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Alejandro Quintanar

13

W wyszukiwarce Google nie znalazłem prostej odpowiedzi, której szukałem. Ale i tak to wymyśliłem. To naprawdę bardzo proste. Postanowiłem go opublikować i być może zapobiec kilku mniejszym zadrapaniom na głowie innej osoby. Klucz jest po prostu „w” i krotkami. Oto zachowanie instrukcji przełączania z funkcją awaryjną, w tym awaryjną 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)

Zapewnia:

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.

Gdzie dokładnie jest ten upadek?
Jonas Schäfer

Ups! Jest tam upadek, ale nie przyczyniam się już do przepełnienia stosu. W ogóle ich NIE lubię. Lubię wkład innych, ale po prostu nie Stackoverflow. Jeśli korzystasz z funkcji fall fall dla FUNKCJONALNOŚCI, to chcesz ŁOWIĆ określone warunki we wszystkich instrukcjach „wszystko w jednym” w przełączniku („złap wszystko”), aż do osiągnięcia instrukcji break w przełączniku.
JD Graham

2
Tutaj zarówno wartości „Pies”, jak i „Kot” spadają i są obsługiwane przez SAMĄ funkcjonalność, czyli są zdefiniowane jako posiadające „cztery nogi”. Jest to STRESZCZENIE równoważne spadaniu i różnym wartościom obsługiwanym przez instrukcję przypadku SAME w przypadku wystąpienia przerwy.
JD Graham

@JDGraham Myślę, że Jonas miał na myśli inny aspekt upadku, który zdarza się, gdy programista czasami zapomina napisać breakna końcu kodu dla case. Ale myślę, że nie potrzebujemy takiego „przełomu” :)
Michaił Batcer

12

Rozwiązania, których używam:

Połączenie 2 opublikowanych tutaj rozwiązań, które jest stosunkowo łatwe do odczytania i obsługuje ustawienia domyślne.

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

gdzie

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

wyszukuje "lambda x: x - 2"w dykcie i używa gox=23

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

nie znajduje go w dykcie i używa domyślnego "lambda x: x - 22"z 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

Rozważ dołączenie krótkiego opisu kodu i sposobu rozwiązania postawionego pytania
Henry Woody,

Okej, teraz dodałem do tego komentarz.
Vikhyat Agarwal

8

Podobała mi się odpowiedź Marka Biesa

Ponieważ xzmienna musi być użyta dwukrotnie, zmodyfikowałem funkcje lambda do bez parametrów.

Muszę uciekać 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'

Edycja: Zauważyłem, że mogę używać Nonepisma z słownikami. To by naśladowałoswitch ; case else


Czy przypadek None nie emuluje po prostu result[None]()?
Bob Stein

Tak, dokładnie. Mam na myśliresult = {'a': 100, None:5000}; result[None]
Guneysus,

4
Sprawdzanie, czy nikt nie myśli, None:zachowuje się tak default:.
Bob Stein

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

Krótki i łatwy do odczytania, ma wartość domyślną i obsługuje wyrażenia zarówno w warunkach, jak i zwracanych wartości.

Jest jednak mniej wydajne niż rozwiązanie ze słownikiem. Na przykład Python musi przejrzeć wszystkie warunki przed zwróceniem wartości domyślnej.


7

możesz użyć wysłanego słownika:

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

Wynik:

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

6

Prosty, nie przetestowany; każdy warunek jest oceniany niezależnie: nie występuje awaria, ale wszystkie przypadki są oceniane (chociaż wyrażenie do włączenia jest oceniane tylko raz), chyba że istnieje instrukcja break. Na przykład,

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

druki Was 1. Was 1 or 2. Was something. (Cholera! Dlaczego nie mogę mieć końcowe spacje w inline bloków kodu?), jeśli expressionocenia się 1, Was 2.jeśli expressionma wartość 2, lub Was something.jeśli expressionma wartość czegoś innego.


1
Cóż, upadek działa, ale tylko przejść do do_default.
syockit

5

Definiowanie:

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

pozwala na użycie dość prostej składni, z przypadkami spakowanymi w mapę:

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

Próbowałem na nowo zdefiniować przełącznik w sposób, który pozwoliłby mi pozbyć się „lambda:”, ale poddałem się. Poprawianie definicji:

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

Pozwoliłem mi odwzorować wiele spraw na ten sam kod i podać domyślną opcję:

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

Każda replikowana sprawa musi znajdować się w swoim własnym słowniku; switch () konsoliduje słowniki przed wyszukaniem wartości. Wciąż jest brzydszy, niż bym chciał, ale ma podstawową efektywność używania haszowanego wyszukiwania wyrażenia, a nie pętli przez wszystkie klawisze.


5

Myślę, że najlepszym sposobem jest użycie idiomów języka Python, aby kod był testowalny . Jak pokazano w poprzednich odpowiedziach, używam słowników, aby korzystać ze struktur i języka Pythona i utrzymywać kod „case” w izolacji na różne sposoby. Poniżej znajduje się klasa, ale możesz użyć bezpośrednio modułu, globałów i funkcji. Klasa ma metody, które można przetestować w izolacji . W zależności od potrzeb możesz także grać metodami statycznymi i atrybutami.

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)

Można skorzystać z tej metody, używając również klas jako kluczy tabeli „__choice_table”. W ten sposób można uniknąć nadużyć związanych z instancją i zachować wszystko w czystości i przetestować.

Załóżmy, że musisz przetworzyć wiele wiadomości lub pakietów z sieci lub Twojego MQ. Każdy pakiet ma własną strukturę i kod zarządzania (w sposób ogólny). Z powyższym kodem można zrobić coś takiego:

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)

Tak więc złożoność nie rozkłada się w przepływie kodu, ale jest renderowana w strukturze kodu .


Naprawdę brzydka ... obudowa czytnika jest tak czysta podczas czytania. Nie mogę zrozumieć, dlaczego nie jest zaimplementowany w Pythonie.
jmcollin92

@AndyClifton: Przepraszam ... przykład? Zastanów się za każdym razem, gdy potrzebujesz wielu kodów rozgałęziających decyzje, i możesz zastosować tę metodę.
J_Zar 11.04.16

@ jmcollin92: oświadczenie zmiany jest wygodne, zgadzam się. Jednak programista ma tendencję do pisania bardzo długich instrukcji i kodu, którego nie można ponownie użyć. Opisany przeze mnie sposób jest łatwiejszy do przetestowania i bardziej przydatny do ponownego użycia, IMHO.
J_Zar 11.04.16

@J_Zar: re. Moja prośba o przykład: tak, rozumiem, ale staram się umieścić to w kontekście większego fragmentu kodu. Czy możesz pokazać, jak mogę to wykorzystać w realnej sytuacji na świecie?
Andy Clifton,

1
@AndyClifton: Przepraszam, jestem spóźniony, ale zamieściłem przykładową sprawę.
J_Zar 20.04.16

5

Rozwijanie odpowiedzi Grega Hewgilla - Możemy zamknąć słownik-rozwiązanie za pomocą dekoratora:

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

Może to być następnie użyte z @case-decoratorem

@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

Dobrą wiadomością jest to, że zostało to już zrobione w module NeoPySwitch. Po prostu zainstaluj za pomocą pip:

pip install NeoPySwitch

5

Rozwiązaniem, z którego zwykle korzystam i które korzysta również ze słowników, jest:

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

Ma to tę zaletę, że nie próbuje za każdym razem oceniać funkcji, a Ty musisz tylko upewnić się, że funkcja zewnętrzna otrzymuje wszystkie informacje, których potrzebują funkcje wewnętrzne.


5

Do tej pory pojawiło się wiele odpowiedzi, które mówiły: „nie mamy przełącznika w Pythonie, zrób to w ten sposób”. Chciałbym jednak zauważyć, że sama instrukcja switch jest konstrukcją łatwą do nadużywania, której można i należy w większości przypadków unikać, ponieważ promują leniwe programowanie. Przykładem:

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

Teraz możesz to zrobić za pomocą instrukcji switch (jeśli Python ją oferuje), ale marnowałbyś swój czas, ponieważ istnieją metody, które robią to dobrze. A może masz coś mniej oczywistego:

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

Jednak tego rodzaju operacje można i należy obsługiwać za pomocą słownika, ponieważ będzie on szybszy, mniej złożony, mniej podatny na błędy i bardziej zwarty.

Zdecydowana większość „przypadków użycia” instrukcji zamiany przypada na jeden z tych dwóch przypadków; jest tylko bardzo mały powód, aby go użyć, jeśli dokładnie przemyślałeś swój problem.

Więc zamiast pytać „jak przełączyć się w Pythonie?”, Być może powinniśmy zapytać: „dlaczego chcę przełączyć się w Pythonie?”. ponieważ jest to często bardziej interesujące pytanie i często ujawnia wady w projektowaniu wszystkiego, co budujesz.

Nie oznacza to, że przełączników nigdy nie należy używać. Maszyny stanowe, leksery, parsery i automaty używają ich do pewnego stopnia i, ogólnie rzecz biorąc, kiedy zaczynasz od symetrycznego wejścia i przechodzisz do asymetrycznego wyjścia, mogą być przydatne; musisz tylko upewnić się, że nie używasz przełącznika jako młotka, ponieważ w kodzie widzisz gwoździe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.