Penggantian untuk pernyataan switch dalam Python?

1718

Saya ingin menulis fungsi dengan Python yang mengembalikan nilai tetap berbeda berdasarkan nilai indeks input.

Dalam bahasa lain saya akan menggunakan pernyataan switchatau case, tetapi Python tampaknya tidak memiliki switchpernyataan. Apa solusi Python yang disarankan dalam skenario ini?

Michael Schneider
sumber
77
Terkait PEP, ditulis oleh Guido sendiri: PEP 3103
chb
28
@ Chb Dalam PEP itu, Guido tidak menyebutkan bahwa jika / elif chain juga merupakan sumber kesalahan klasik. Itu adalah konstruksi yang sangat rapuh.
karen
15
Hilang dari semua solusi di sini adalah deteksi nilai kasus duplikat . Sebagai prinsip gagal-cepat, ini mungkin kerugian yang lebih penting daripada kinerja atau fitur terobosan.
Bob Stein
6
switchsebenarnya lebih "serbaguna" daripada sesuatu yang mengembalikan nilai tetap berbeda berdasarkan nilai indeks input. Ini memungkinkan berbagai potongan kode dieksekusi. Sebenarnya tidak perlu mengembalikan nilai. Saya bertanya-tanya apakah beberapa jawaban di sini adalah pengganti yang baik untuk switchpernyataan umum , atau hanya untuk kasus pengembalian nilai tanpa kemungkinan mengeksekusi potongan kode umum.
sancho.s ReinstateMonicaCellio
3
@ MalikA.Rumi Konstruksi rapuh, sama seperti loop sementara adalah konstruksi rapuh jika Anda mencoba menggunakannya untuk melakukan apa yang ... di ... lakukan. Apakah Anda akan memanggil pemrogram lemah untuk menggunakan untuk loop? Sementara loop adalah semua yang sebenarnya mereka butuhkan. Tetapi untuk loop menunjukkan niat yang jelas, simpan boilerplate sia-sia dan berikan kesempatan untuk membuat abstraksi yang kuat.
itsbruce

Jawaban:

1486

Anda bisa menggunakan kamus:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
Greg Hewgill
sumber
100
Apa yang terjadi jika x tidak ditemukan?
Nick
46
@nick: Anda dapat menggunakan defaultdict
Eli Bendersky
385
Saya akan merekomendasikan menempatkan dikt di luar fungsi jika kinerja merupakan masalah, sehingga tidak membangun kembali dikt pada setiap pemanggilan fungsi
Claudiu
56
@EliBendersky, Menggunakan getmetode ini mungkin akan lebih normal daripada menggunakan collections.defaultdictdalam kasus ini.
Mike Graham
27
@Nick, Pengecualian dilemparkan — lakukan }.get(x, default)sebaliknya jika harus ada default. (Catatan: ini jauh lebih baik daripada apa yang terjadi jika Anda membiarkan default dari pernyataan switch!)
Mike Graham
1375

Jika Anda ingin default, Anda bisa menggunakan get(key[, default])metode kamus :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found
Nick
sumber
11
Bagaimana jika 'a' dan 'b' cocok dengan 1, dan 'c' dan 'd' cocok dengan 2?
John Mee
13
@ JK: Ya, jelas pencarian kamus tidak mendukung fall-throughs. Anda dapat melakukan pencarian kamus ganda. Yaitu 'a' & 'b' poin ke answer1 dan 'c' dan 'd' point to answer2, yang terkandung dalam kamus kedua.
Nick
3
ini lebih baik untuk melewatkan nilai default
HaTiMSuM
Ada masalah dengan pendekatan ini, pertama setiap kali Anda menelepon jika Anda akan membuat dict lagi kedua jika Anda memiliki nilai yang lebih kompleks Anda bisa mendapatkan pengecualian ex. jika x adalah sebuah tupel dan kami ingin melakukan sesuatu seperti ini x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Ini akan menaikkan IndexError
Idan Haim Shalom
2
@Idan: Pertanyaannya adalah untuk meniru saklar. Saya yakin saya dapat memecahkan kode ini juga jika saya mencoba memasukkan nilai aneh. Ya, itu akan dibuat ulang, tetapi mudah diperbaiki.
Nick
394

Saya selalu suka melakukannya dengan cara ini

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

Dari sini

Mark Biek
sumber
16
Metode yang besar, dikombinasikan dengan get () untuk menangani default adalah pilihan terbaik saya juga
drAlberT
27
mungkin bukan ide yang baik untuk menggunakan lambda dalam kasus ini karena lambda sebenarnya disebut setiap kali kamus dibuat.
Asher
13
Sayangnya ini adalah orang terdekat yang akan didapat. Metode yang menggunakan .get()(seperti jawaban tertinggi saat ini) perlu dengan bersemangat mengevaluasi semua kemungkinan sebelum pengiriman, dan karena itu tidak hanya (tidak hanya sangat tetapi) sangat tidak efisien dan juga tidak dapat memiliki efek samping; jawaban ini mengatasi masalah itu, tetapi lebih bersifat verbal. Saya hanya akan menggunakan if / elif / else, dan bahkan mereka butuh waktu lama untuk menulis sebagai 'case'.
ninjagecko
13
bukankah ini akan mengevaluasi semua fungsi / lambda setiap kali dalam semua kasus, bahkan jika itu hanya mengembalikan salah satu hasil?
slf
23
@ slf Tidak, ketika aliran kontrol mencapai potongan kode, itu akan membangun 3 fungsi (melalui penggunaan 3 lambdas) dan kemudian membangun kamus dengan 3 fungsi tersebut sebagai nilai, tetapi mereka tetap tidak layak ( evaluasi sedikit ambigu dalam konteks itu) pada awalnya. Kemudian kamus diindeks melalui [value], yang akan mengembalikan hanya satu dari 3 fungsi (dengan asumsi valueadalah salah satu dari 3 kunci). Fungsi belum dipanggil pada saat itu. Kemudian (x)panggil fungsi yang baru saja dikembalikan dengan xargumen (dan hasilnya menuju ke result). 2 fungsi lainnya tidak akan dipanggil.
blubberdiblub
354

Selain metode kamus (yang sangat saya sukai, BTW), Anda juga dapat menggunakan if- elif- elseuntuk mendapatkan fungsi 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

Ini tentu saja tidak identik dengan beralih / kasing - Anda tidak dapat terjatuh semudah meninggalkan breakpernyataan, tetapi Anda dapat memiliki tes yang lebih rumit. Pemformatannya lebih bagus daripada serangkaian nested ifs, meskipun secara fungsional itulah yang lebih dekat dengannya.

Matthew Schinckel
sumber
51
Saya benar-benar lebih suka ini, menggunakan konstruksi bahasa standar dan tidak melempar KeyError jika tidak ada kasus yang cocok ditemukan
martyglaubitz
7
Saya berpikir tentang kamus / getcara, tetapi cara standar lebih mudah dibaca.
Martin Thoma
2
@someuser tetapi kenyataannya mereka bisa "tumpang tindih" adalah fitur. Anda hanya memastikan urutan adalah prioritas di mana pertandingan harus terjadi. Adapun x berulang: lakukan saja x = the.other.thingsebelumnya. Biasanya, Anda akan memiliki satu if, multiple elif dan single lainnya, karena itu lebih mudah dipahami.
Matthew Schinckel
7
Bagus, "Jatuh dengan tidak menggunakan Elif" agak membingungkan, meskipun. Bagaimana dengan ini: lupakan "jatuh melalui" dan hanya menerimanya sebagai dua if/elif/else?
Alois Mahdal
7
Juga layak disebutkan, ketika menggunakan hal-hal seperti x in 'bc', perlu diingat bahwa "" in "bc"ini True.
Lohmar ASHAR
185

Resep Python favorit saya untuk sakelar / kasing adalah:

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

Singkat dan sederhana untuk skenario sederhana.

Bandingkan dengan 11+ baris kode C:

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

Anda bahkan dapat menetapkan beberapa variabel dengan menggunakan tuple:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
ChaimG
sumber
16
Saya menemukan ini menjadi jawaban yang lebih kuat daripada yang diterima.
cerd
3
@some user: C mensyaratkan bahwa nilai balik menjadi tipe yang sama untuk semua kasus. Python tidak. Saya ingin menyoroti fleksibilitas Python ini untuk berjaga-jaga seandainya seseorang memiliki situasi yang memerlukan penggunaan tersebut.
ChaimG
3
@saya pengguna: Secara pribadi, saya menemukan {} .get (,) dapat dibaca. Untuk keterbacaan ekstra untuk pemula Python, Anda mungkin ingin menggunakannya default = -1; result = choices.get(key, default).
ChaimG
4
bandingkan dengan 1 baris c ++result=key=='a'?1:key==b?2:-1
Jasen
4
@Jasen satu dapat mengatakan bahwa Anda dapat melakukannya dalam satu baris Python juga: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). tetapi apakah satu liner dapat dibaca?
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))

Pemakaian:

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

Tes:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
adamh
sumber
64
Ini bukan ancaman yang aman. Jika beberapa sakelar dipukul secara bersamaan, semua sakelar mengambil nilai sakelar terakhir.
francescortiz
48
Meskipun @francescortiz kemungkinan berarti utas aman, itu juga tidak aman. Ini mengancam nilai-nilai variabel!
Zizouz212
7
Masalah keamanan benang kemungkinan bisa diatasi dengan menggunakan penyimpanan lokal-benang . Atau bisa dihindari sama sekali dengan mengembalikan sebuah instance dan menggunakan instance itu untuk perbandingan kasus.
blubberdiblub
6
@blubberdiblub Tapi bukankah lebih efisien menggunakan ifpernyataan standar ?
wizzwizz4
9
Ini juga tidak aman jika digunakan dalam banyak fungsi. Dalam contoh yang diberikan, jika case(2)blok memanggil fungsi lain yang menggunakan switch (), maka ketika melakukan case(2, 3, 5, 7)dll untuk mencari kasus selanjutnya yang akan dieksekusi, ia akan menggunakan nilai sakelar yang diatur oleh fungsi lain bukan yang disetel oleh pernyataan sakelar saat ini .
user9876
52

Resep favorit saya adalah resep yang sangat enak . Anda akan sangat menyukainya. Ini yang paling dekat yang pernah saya lihat dengan pernyataan kasus sakelar aktual, terutama di fitur.

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

Ini sebuah contoh:

# 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!"
John Doe
sumber
3
Saya akan menggantikannya for case in switch()dengan with switch() as case, lebih masuk akal, karena itu perlu dijalankan hanya sekali.
Ski
4
@Skirmantas: Catatan yang withtidak memungkinkan untuk break, jadi opsi fallthrough diambil.
Jonas Schäfer
5
Permintaan maaf karena tidak berusaha lebih keras untuk menentukannya sendiri: jawaban serupa di atas tidak aman untuk thread. Apakah ini?
David Winiecki
1
@ Davidvidiniecki Komponen kode yang hilang dari di atas (dan mungkin hak cipta berdasarkan aktivasi) tampaknya aman utas.
Jasen
apakah versi lain dari ini akan menjadi seperti 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
Ian Bell
sumber
9
Menggunakan manajer konteks adalah solusi kreatif yang baik. Saya akan merekomendasikan menambahkan sedikit penjelasan dan mungkin tautan ke beberapa informasi tentang Manajer Konteks untuk memberikan posting ini beberapa, well, konteks;)
Will
2
Saya tidak suka jika / elif rantai banyak tetapi ini adalah yang paling kreatif dan paling praktis dari semua solusi yang saya lihat menggunakan sintaksis Python yang ada.
itsbruce
2
Ini sangat bagus. Satu perbaikan yang disarankan adalah menambahkan properti (publik) valueke kelas Switch sehingga Anda bisa referensi case.valuedalam pernyataan itu.
Peter
48

Ada pola yang saya pelajari dari kode 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'

Anda dapat menggunakannya kapan saja Anda perlu mengirim token dan mengeksekusi sepotong kode yang diperluas. Di mesin negara Anda akan memiliki state_metode, dan pengiriman self.state. Switch ini dapat diperluas dengan mewarisi dari kelas dasar dan mendefinisikan do_metode Anda sendiri . Sering kali Anda bahkan tidak akan memiliki do_metode di kelas dasar.

Sunting: bagaimana tepatnya itu digunakan

Dalam hal SMTP, Anda akan menerima HELOdari kawat. Kode yang relevan (dari twisted/mail/smtp.py, dimodifikasi untuk kasus kami) terlihat seperti ini

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

Anda akan menerima ' HELO foo.bar.com '(atau Anda mungkin mendapatkan 'QUIT'atau 'RCPT TO: foo'). Ini tokenized menjadi partssebagai ['HELO', 'foo.bar.com']. Nama pencarian metode yang sebenarnya diambil dari parts[0].

(Metode asli juga disebut state_COMMAND, karena menggunakan pola yang sama untuk mengimplementasikan mesin negara, yaitu getattr(self, 'state_' + self.mode))


sumber
4
Saya tidak melihat manfaat dari pola ini hanya dengan memanggil metode secara langsung: SMTP (). Do_HELO ('foo.bar.com') OK, mungkin ada kode umum dalam lookupMethod, tetapi karena itu juga dapat ditimpa oleh subkelas saya tidak melihat apa yang Anda dapatkan dari tipuan.
Tuan Shark
1
Anda tidak akan tahu metode apa yang harus dihubungi di muka, yaitu 'HELO' berasal dari variabel. Saya telah menambahkan contoh penggunaan ke posting asli
Boleh saya sarankan secara sederhana: eval ('SMTP (). Do_' + command) ('foo.bar.com')
jforberg
8
eval? serius? dan alih-alih meng-instantiate satu metode per panggilan, kita dapat membuat instantiate sekali dan menggunakannya dalam semua panggilan asalkan tidak memiliki keadaan internal.
Mahesh
1
IMO kunci sebenarnya di sini adalah pengiriman menggunakan getattr untuk menentukan fungsi yang akan dijalankan. Jika metode ada dalam modul, Anda bisa melakukan getattr (lokal (), func_name) untuk mendapatkannya. Bagian 'do_' baik untuk keamanan / kesalahan sehingga hanya fungsi dengan awalan yang bisa dipanggil. SMTP sendiri memanggil lookupMethod. Idealnya pihak luar tidak tahu tentang semua ini. Tidak masuk akal untuk melakukan SMTP (). LookupMethod (nama) (data). Karena perintah dan data berada dalam satu string dan SMTP mem-parsingnya, itu lebih masuk akal. Terakhir, SMTP mungkin memiliki status bersama lainnya yang membenarkannya sebagai kelas.
ShawnFumo
27

Katakanlah Anda tidak ingin hanya mengembalikan nilai, tetapi ingin menggunakan metode yang mengubah sesuatu pada suatu objek. Menggunakan pendekatan yang dinyatakan di sini adalah:

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

Apa yang terjadi di sini adalah python mengevaluasi semua metode dalam kamus. Jadi, bahkan jika nilai Anda adalah 'a', objek akan bertambah dan dikurangi dengan x.

Larutan:

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

result = func(*args)

Jadi, Anda mendapatkan daftar yang berisi fungsi dan argumennya. Dengan cara ini, hanya pointer fungsi dan daftar argumen yang dikembalikan, tidak dievaluasi. 'result' kemudian mengevaluasi pemanggilan fungsi yang dikembalikan.

Astaga
sumber
23

Saya hanya akan menurunkan dua sen saya di sini. Alasan tidak ada pernyataan case / switch dalam Python adalah karena Python mengikuti prinsip 'Hanya ada satu cara yang tepat untuk melakukan sesuatu'. Jadi jelas Anda bisa membuat berbagai cara untuk menciptakan fungsi switch / case, tetapi cara Pythonic untuk mencapai ini adalah if / elif construct. yaitu

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

Saya hanya merasa PEP 8 layak mendapat anggukan di sini. Salah satu hal indah tentang Python adalah kesederhanaan dan keanggunannya. Itu sebagian besar berasal dari prinsip-prinsip yang tercantum dalam PEP 8, termasuk "Hanya ada satu cara yang tepat untuk melakukan sesuatu"

pengguna2233949
sumber
6
Jadi mengapa Python memiliki loop dan while loop? Segala sesuatu yang dapat Anda lakukan dengan for loop dapat Anda terapkan dengan loop sementara.
bruce
1
Benar. Switch / case terlalu sering disalahgunakan oleh programmer pemula. Apa yang sebenarnya mereka inginkan adalah pola strategi .
user228395
Kedengarannya seperti Python berharap itu Clojure
TWR Cole
1
@ THWRCole Saya tidak berpikir begitu, Python melakukannya terlebih dahulu. Python sudah ada sejak 1990 dan Clojure sejak 2007.
Taylor
Hanya ada satu cara yang tepat untuk melakukan sesuatu. Python 2.7 atau Python 3? Lol.
TWR Cole
17

memperluas gagasan "dict as switch". jika Anda ingin menggunakan nilai default untuk sakelar Anda:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
Jeremy Cantrell
sumber
14
Saya pikir lebih jelas untuk menggunakan .get () pada dict dengan default yang ditentukan. Saya lebih suka meninggalkan Pengecualian untuk keadaan luar biasa, dan memotong tiga baris kode dan tingkat lekukan tanpa menjadi tidak jelas.
Chris B.
10
Ini adalah keadaan yang luar biasa. Ini mungkin atau mungkin bukan keadaan yang langka tergantung pada yang berguna, tapi itu pasti pengecualian (mundur 'default') dari aturan (dapatkan sesuatu dari dikt ini). Secara desain, program Python menggunakan pengecualian dengan mudah. Yang sedang berkata, menggunakan getberpotensi dapat membuat kode sedikit lebih baik.
Mike Graham
16

Jika Anda memiliki blok kasus yang rumit, Anda dapat mempertimbangkan menggunakan tabel pencarian kamus fungsi ...

Jika Anda belum melakukan ini sebelumnya, ide yang bagus untuk masuk ke debugger dan lihat bagaimana kamus melihat setiap fungsi.

CATATAN: Do tidak menggunakan "()" dalam kasus / kamus lookup atau akan memanggil masing-masing fungsi Anda sebagai blok kamus / kasus dibuat. Ingat ini karena Anda hanya ingin memanggil setiap fungsi sekali menggunakan pencarian gaya 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()
Asher
sumber
Saya suka solusi Anda. Tetapi, bagaimana jika saya hanya perlu melewatkan beberapa variabel atau objek?
Tedo Vrbanec
Ini tidak akan berfungsi jika metode mengharapkan parameter.
Kulasangar
16

Jika Anda mencari pernyataan ekstra, sebagai "switch", saya membangun modul python yang memperluas Python. Ini disebut ESPY sebagai "Enhanced Structure for Python" dan tersedia untuk Python 2.x dan Python 3.x.

Misalnya, dalam hal ini, pernyataan switch dapat dilakukan dengan kode berikut:

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

yang bisa digunakan seperti ini:

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

jadi tanyalah menerjemahkannya dengan Python sebagai:

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
elp
sumber
Sangat keren, tapi apa gunanya while True:di bagian atas kode Python yang dihasilkan? Ini pasti akan mengenai bagian breakbawah kode Python yang dihasilkan, jadi menurut saya keduanya while True:dan breakdapat dihapus. Lebih lanjut, apakah ESPY cukup pintar untuk mengubah nama contjika pengguna menggunakan nama yang sama dalam kode mereka sendiri? Bagaimanapun, saya ingin menggunakan vanilla Python jadi saya tidak akan menggunakan ini, tapi tidak masalah. +1 untuk kesejukan belaka.
ArtOfWarfare
@ArtOfWarfare Alasan untuk while True:dan breaks adalah untuk mengizinkan tetapi tidak memerlukan penerusan .
Solomon Ucko
Apakah modul ini masih tersedia?
Solomon Ucko
15

Saya menemukan bahwa struktur saklar umum:

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

dapat diekspresikan dalam Python sebagai berikut:

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

atau diformat dengan cara yang lebih jelas:

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

Alih-alih menjadi pernyataan, versi python adalah ekspresi, yang mengevaluasi nilai.

Leo
sumber
Juga bukannya ... parameter ... dan p1 (x) bagaimana parameterdanp1==parameter
Bob Stein
@ BobStein-VisiBone hi, di sini adalah contoh yang berjalan di sesi python saya: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Ketika saya kemudian menelepon f(2), saya mendapat 'c'; f(1), 'b'; dan f(0), 'a'. Adapun p1 (x), ini menunjukkan predikat; selama ia kembali Trueatau False, tidak peduli itu panggilan fungsi atau ekspresi, tidak apa-apa.
leo
@ BobStein-VisiBone Ya, Anda benar! Terima kasih :) Agar ungkapan multi-baris berfungsi, tanda kurung harus ditempatkan, seperti dalam saran Anda, atau seperti dalam contoh saya yang dimodifikasi.
leo
Luar biasa. Sekarang saya akan menghapus semua komentar saya tentang parens.
Bob Stein
15

Sebagian besar jawaban di sini sudah cukup lama, dan terutama yang diterima, jadi sepertinya layak diperbarui.

Pertama, FAQ Python resmi membahas hal ini, dan merekomendasikan elifrantai untuk kasus sederhana dan dictuntuk kasus yang lebih besar atau lebih kompleks. Ini juga menyarankan serangkaian visit_metode (gaya yang digunakan oleh banyak kerangka kerja server) untuk beberapa kasus:

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

FAQ juga menyebutkan PEP 275 , yang ditulis untuk mendapatkan keputusan resmi sekali-untuk-semua tentang penambahan pernyataan beralih gaya-C. Tetapi PEP itu sebenarnya ditangguhkan ke Python 3, dan hanya secara resmi ditolak sebagai proposal terpisah, PEP 3103 . Jawabannya tentu saja tidak — tetapi kedua PEP memiliki tautan ke informasi tambahan jika Anda tertarik pada alasan atau sejarahnya.


Satu hal yang muncul beberapa kali (dan dapat dilihat dalam PEP 275, meskipun itu dipotong sebagai rekomendasi sebenarnya) adalah bahwa jika Anda benar-benar terganggu dengan memiliki 8 baris kode untuk menangani 4 kasus, vs. 6 baris yang Anda miliki di C atau Bash, Anda selalu dapat menulis ini:

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

Ini tidak sepenuhnya didorong oleh PEP 8, tetapi itu dapat dibaca dan tidak terlalu tidak otomatis.


Selama lebih dari satu dekade sejak PEP 3103 ditolak, masalah pernyataan kasus C-style, atau bahkan versi yang sedikit lebih kuat di Go, telah dianggap mati; setiap kali ada yang membawanya pada python-ideas atau -dev, mereka dirujuk ke keputusan lama.

Namun, gagasan pencocokan pola gaya ML penuh muncul setiap beberapa tahun, terutama karena bahasa seperti Swift dan Rust telah mengadopsinya. Masalahnya adalah sulit untuk menggunakan banyak dari pencocokan pola tanpa tipe data aljabar. Sementara Guido bersimpati pada ide tersebut, tidak ada yang datang dengan proposal yang cocok dengan Python. (Anda dapat membaca strawman 2014 saya sebagai contoh.) Ini bisa berubah dengan dataclassdi 3.7 dan beberapa proposal sporadis untuk yang lebih kuat enumuntuk menangani jenis penjumlahan, atau dengan berbagai proposal untuk berbagai jenis ikatan lokal-pernyataan (seperti PEP 3150 , atau set proposal yang saat ini sedang dibahas di -ideas). Tapi sejauh ini, belum.

Ada juga kadang-kadang proposal untuk pencocokan gaya 6-Perl, yang pada dasarnya merupakan mishmash dari segalanya mulai dari elifregex ke jenis-pengiriman tunggal-switching.

abarnert
sumber
15

Solusi untuk menjalankan fungsi:

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

di mana foo1 (), foo2 (), foo3 () dan default () adalah fungsi

Alejandro Quintanar
sumber
1
Ya, misalnya jika opsi variabel Anda == "case2" hasil Anda = foo2 ()
Alejandro Quintanar
dan seterusnya.
Alejandro Quintanar
Ya, saya mengerti tujuannya. Tetapi kekhawatiran saya adalah bahwa jika Anda hanya ingin foo2(), fungsi foo1(), foo3()dan default()fungsi semuanya juga akan berjalan, artinya segalanya bisa memakan waktu lama
Brian Underwood
1
hilangkan tanda () di dalam kamus. gunakan get(option)(). masalah terpecahkan.
timgeb
1
Sangat baik penggunaan () adalah solusi parut, saya membuat inti untuk mengujinya gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Alejandro Quintanar
13

Saya tidak menemukan jawaban sederhana yang saya cari di mana saja di pencarian Google. Tapi toh aku yang menemukannya. Ini sangat sederhana. Memutuskan untuk mempostingnya, dan mungkin mencegah goresan di kepala orang lain lebih sedikit. Kuncinya hanyalah "dalam" dan tupel. Berikut adalah perilaku pernyataan switch dengan fall-through, termasuk 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)

Menyediakan:

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.
JD Graham
sumber
Di mana tepatnya fallthrough di sini?
Jonas Schäfer
Ups! Ada kejatuhan di sana, tapi saya tidak berkontribusi pada Stack Overflow lagi. Tidak suka MEREKA sama sekali. Saya suka kontribusi orang lain, tetapi tidak Stackoverflow. Jika Anda menggunakan fall through untuk FUNCTIONALITY maka Anda ingin MENCAPAI kondisi tertentu di semua dalam satu pernyataan kasus di sakelar (tangkapan semua), hingga Anda mencapai pernyataan istirahat di sakelar.
JD Graham
2
Di sini baik nilai "Dog" dan "Cat" JATUH MELALUI dan ditangani oleh fungsi SAMA, yang mereka didefinisikan sebagai memiliki "empat kaki." Ini setara dengan abstrak untuk jatuh dan nilai yang berbeda ditangani oleh pernyataan kasus SAMA di mana istirahat terjadi.
JD Graham
@ JDGraham Saya pikir Jonas berarti aspek lain dari fallthrough, yang terjadi ketika programmer kadang-kadang lupa untuk menulis breakdi akhir kode untuk a case. Tapi saya pikir kita tidak perlu seperti "fallthrough" :)
Mikhail Batcer
12

Solusi yang saya gunakan:

Kombinasi 2 solusi yang diposting di sini, yang relatif mudah dibaca dan mendukung default.

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

dimana

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

melihat "lambda x: x - 2"dict dan menggunakannya denganx=23

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

tidak menemukannya di dict dan menggunakan default "lambda x: x - 22"dengan x=44.

thomasf1
sumber
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
pengguna5224656
sumber
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
Vikhyat Agarwal
sumber
Pertimbangkan untuk memasukkan deskripsi singkat tentang kode Anda dan cara memecahkan pertanyaan yang diposting
Henry Woody
Oke, saya sudah menambahkan komentar untuk itu sekarang.
Vikhyat Agarwal
8

aku suka jawaban Mark Bies

Sejak x variabel harus digunakan dua kali, saya memodifikasi fungsi lambda menjadi parameterless.

Saya harus menjalankannya 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'

Sunting: Saya perhatikan bahwa saya dapat menggunakan Nonejenis dengan kamus. Jadi ini akan ditiruswitch ; case else

guneysus
sumber
Bukankah kasus None meniru dengan sederhana result[None]()?
Bob Stein
Ya persis. Maksud sayaresult = {'a': 100, None:5000}; result[None]
guneysus
4
Hanya memeriksa tidak ada yang berpikir None:berperilaku seperti default:.
Bob Stein
7
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Singkat dan mudah dibaca, memiliki nilai default dan mendukung ekspresi di kedua kondisi dan mengembalikan nilai.

Namun, ini kurang efisien daripada solusi dengan kamus. Misalnya, Python harus memindai semua kondisi sebelum mengembalikan nilai default.

emu
sumber
7

Anda dapat menggunakan dict yang dikirim:

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

Keluaran:

This is case 1
This is case 3
This is case 2
This is case 1
Felix Martinez
sumber
6

Sederhana, tidak diuji; setiap kondisi dievaluasi secara independen: tidak ada jatuh, tetapi semua kasus dievaluasi (meskipun ekspresi untuk menghidupkan hanya dievaluasi sekali), kecuali ada pernyataan istirahat. Sebagai contoh,

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

cetakan Was 1. Was 1 or 2. Was something. (Sial! Mengapa saya tidak dapat memiliki spasi spasi di blok kode sebaris?) jika expressiondievaluasi 1, Was 2.jika expressiondievaluasi 2, atau Was something.jika expressiondievaluasi ke yang lain.

Solomon Ucko
sumber
1
Yah, gagal, tapi hanya pergi ke do_default.
syockit
5

Mendefinisikan:

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

memungkinkan Anda untuk menggunakan sintaks yang cukup mudah, dengan case yang dibundel ke dalam peta:

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

Saya terus mencoba mendefinisikan ulang saklar dengan cara yang akan membiarkan saya menyingkirkan "lambda:", tetapi menyerah. Tweak definisi:

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

Mengizinkan saya memetakan banyak kasing ke kode yang sama, dan untuk menyediakan opsi default:

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

Setiap case yang direplikasi harus dalam kamusnya sendiri; switch () mengkonsolidasikan kamus sebelum mencari nilainya. Ini masih lebih jelek daripada yang saya inginkan, tetapi memiliki efisiensi dasar menggunakan pencarian hash pada ekspresi, daripada loop melalui semua kunci.

William H. Hooper
sumber
5

Saya pikir cara terbaik adalah dengan menggunakan idiom bahasa python untuk menjaga kode Anda dapat diuji . Seperti yang ditunjukkan dalam jawaban sebelumnya, saya menggunakan kamus untuk memanfaatkan struktur dan bahasa python dan menjaga kode "case" tetap terisolasi dalam metode yang berbeda. Di bawah ini ada kelas, tetapi Anda dapat menggunakan modul, global, dan fungsi secara langsung. Kelas memiliki metode yang dapat diuji dengan isolasi . Bergantung pada kebutuhan Anda, Anda dapat bermain dengan metode dan atribut statis juga.

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)

Dimungkinkan untuk mengambil keuntungan dari metode ini menggunakan juga kelas sebagai kunci "__choice_table". Dengan cara ini Anda dapat menghindari penyalahgunaan konten dan menjaga semua tetap bersih dan dapat diuji.

Andaikan Anda harus memroses banyak pesan atau paket dari internet atau MQ Anda. Setiap paket memiliki struktur dan kode manajemennya sendiri (dengan cara yang umum). Dengan kode di atas dimungkinkan untuk melakukan sesuatu seperti ini:

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)

Jadi kompleksitas tidak menyebar dalam aliran kode tetapi ini diberikan dalam struktur kode .

J_Zar
sumber
Benar-benar jelek ... saklar case sangat bersih saat membaca. Tidak dapat memahami mengapa itu tidak diterapkan dalam Python.
jmcollin92
@AndyClifton: Maafkan saya ... contoh? Pikirkan setiap kali Anda perlu memiliki beberapa kode keputusan cabang dan Anda dapat menerapkan metode ini.
J_Zar
@ jmcollin92: pernyataan switch itu nyaman, saya setuju. Namun programmer cenderung menulis pernyataan dan kode yang sangat panjang yang tidak dapat digunakan kembali. Cara saya jelaskan lebih bersih untuk menguji dan lebih dapat digunakan kembali, IMHO.
J_Zar
@ J_Zar: re. Permintaan saya untuk sebuah contoh: ya, saya mengerti, tapi saya berjuang untuk menempatkan ini dalam konteks kode yang lebih besar. Bisakah Anda menunjukkan bagaimana saya bisa menggunakan ini dalam situasi dunia nyata?
Andy Clifton
1
@AndyClifton: Maaf, saya terlambat tapi saya memposting beberapa contoh kasus.
J_Zar
5

Memperluas jawaban Greg Hewgill - Kita dapat merangkum solusi kamus menggunakan dekorator:

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

Ini kemudian dapat digunakan dengan @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

Kabar baiknya adalah bahwa ini sudah dilakukan di NeoPySwitch -module. Cukup instal menggunakan pip:

pip install NeoPySwitch
Tom
sumber
5

Solusi yang cenderung saya gunakan yang juga menggunakan kamus adalah:

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

Ini memiliki keuntungan karena tidak mencoba untuk mengevaluasi fungsi setiap waktu, dan Anda hanya perlu memastikan bahwa fungsi luar mendapatkan semua informasi yang dibutuhkan fungsi dalam.

Tony Suffolk 66
sumber
5

Ada banyak jawaban sejauh ini yang mengatakan, "kami tidak memiliki saklar di Python, lakukan dengan cara ini". Namun, saya ingin menunjukkan bahwa pernyataan switch itu sendiri adalah konstruksi yang mudah disalahgunakan yang dapat dan harus dihindari dalam kebanyakan kasus karena mereka mempromosikan pemrograman malas. Inti masalah:

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

Sekarang, Anda bisa melakukan ini dengan pernyataan switch (jika Python menawarkan satu) tetapi Anda akan membuang-buang waktu karena ada metode yang melakukan ini dengan baik. Atau mungkin, Anda memiliki sesuatu yang kurang jelas:

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

Namun, operasi semacam ini dapat dan harus ditangani dengan kamus karena akan lebih cepat, lebih kompleks, lebih rentan terhadap kesalahan dan lebih kompak.

Dan sebagian besar "kasus penggunaan" untuk pernyataan beralih akan jatuh ke dalam salah satu dari dua kasus ini; hanya ada sedikit alasan untuk menggunakannya jika Anda sudah memikirkan masalah Anda secara menyeluruh.

Jadi, daripada bertanya "bagaimana cara beralih di Python?", Mungkin kita harus bertanya, "mengapa saya ingin beralih di Python?" karena itu seringkali pertanyaan yang lebih menarik dan akan sering memaparkan kekurangan dalam desain apa pun yang sedang Anda bangun.

Sekarang, itu bukan untuk mengatakan bahwa switch tidak boleh digunakan juga. Mesin negara, lexer, parser, dan automata semuanya menggunakannya sampai tingkat tertentu dan, secara umum, ketika Anda mulai dari input simetris dan pergi ke output asimetris mereka dapat berguna; Anda hanya perlu memastikan bahwa Anda tidak menggunakan sakelar sebagai palu karena Anda melihat banyak paku dalam kode Anda.

Woody1193
sumber