การเปลี่ยนคำสั่งสวิตช์ใน Python?


1718

ฉันต้องการเขียนฟังก์ชั่นใน Python ที่ส่งกลับค่าคงที่ที่แตกต่างกันตามค่าของดัชนีอินพุท

ในภาษาอื่นฉันจะใช้ a switchหรือcasestatement แต่ Python ไม่ปรากฏว่ามีswitchstatement โซลูชัน Python ที่แนะนำในสถานการณ์นี้คืออะไร


77
PEP ที่เกี่ยวข้องซึ่งเขียนโดย Guido เอง: PEP 3103
chb

28
@chb ใน PEP นั้น Guido ไม่ได้กล่าวถึงว่าหาก / elif chains เป็นแหล่งของข้อผิดพลาดแบบคลาสสิก มันเป็นโครงสร้างที่บอบบางมาก
itsbruce

15
ที่ขาดหายไปจากการแก้ปัญหาทั้งหมดที่นี่คือการตรวจสอบของค่ากรณีที่ซ้ำกัน ในฐานะที่เป็นหลักการที่ล้มเหลวอย่างรวดเร็วสิ่งนี้อาจเป็นการสูญเสียที่สำคัญกว่าประสิทธิภาพหรือคุณลักษณะการเลื่อนขั้น
Bob Stein

6
switchแท้จริงแล้วคือ "อเนกประสงค์" มากกว่าสิ่งที่คืนค่าคงที่ที่แตกต่างกันตามค่าของดัชนีอินพุท อนุญาตให้เรียกใช้งานโค้ดที่แตกต่างกัน มันไม่จำเป็นต้องคืนค่า ฉันสงสัยว่าคำตอบบางคำในที่นี้เป็นการแทนที่ที่ดีสำหรับswitchคำสั่งทั่วไปหรือเฉพาะกรณีที่ส่งคืนค่าโดยไม่มีความเป็นไปได้ในการเรียกใช้งานโค้ดทั่วไป
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Fragile construct เหมือนกับว่า while loop เป็นโครงสร้างที่บอบบางหากคุณลองใช้มันเพื่อทำสิ่งที่ ... ใน ... ทำ คุณจะโทรหาโปรแกรมเมอร์ที่อ่อนแอสำหรับการใช้ลูปหรือไม่? ในขณะที่ลูปเป็นสิ่งที่พวกเขาต้องการจริงๆ แต่สำหรับลูปแสดงเจตนาที่ชัดเจนประหยัดบันทึกสำเร็จรูปและให้โอกาสในการสร้าง abstractions ที่มีประสิทธิภาพ
itsbruce

คำตอบ:


1486

คุณสามารถใช้พจนานุกรม:

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

100
จะเกิดอะไรขึ้นหากไม่พบ x
Nick

46
@nick: คุณสามารถใช้ defaultdict
Eli Bendersky

385
ฉันขอแนะนำให้วาง dict นอกฟังก์ชั่นหากประสิทธิภาพเป็นปัญหาดังนั้นจึงไม่สร้าง dict ใหม่ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน
Claudiu

56
@EliBendersky การใช้getวิธีนี้อาจเป็นเรื่องปกติมากกว่าการใช้ a collections.defaultdictในกรณีนี้
Mike Graham

27
@Nick ข้อยกเว้นจะถูกโยน - ทำ}.get(x, default)แทนหากควรมีค่าเริ่มต้น (หมายเหตุ: นี่คือสิ่งที่ดีกว่าถ้าคุณปล่อยค่าเริ่มต้นจากคำสั่งสวิตช์!)
Mike Graham

1375

หากคุณต้องการค่าเริ่มต้นคุณสามารถใช้get(key[, default])วิธีการพจนานุกรม:

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

11
จะเกิดอะไรขึ้นถ้า 'a' และ 'b' ตรงกับ 1 และ 'c' และ 'd' ตรงกับ 2
John Mee

13
@JM: การค้นหาพจนานุกรมชัด ๆ ไม่สนับสนุนการตกหล่น คุณสามารถทำการค้นหาพจนานุกรมสองครั้ง เช่น 'a' & 'b' ชี้ไปที่ answer1 และ 'c' และ 'd' ชี้ไปที่ answer2 ซึ่งมีอยู่ในพจนานุกรมที่สอง
Nick

3
นี่เป็นการดีกว่าที่จะผ่านค่าเริ่มต้น
HaTiMSuM

มีปัญหากับวิธีการนี้เป็นครั้งแรกในแต่ละครั้งที่คุณโทรหาคุณจะสร้าง dict อีกครั้งที่สองหากคุณมีค่าที่ซับซ้อนมากขึ้นคุณจะได้รับข้อยกเว้น ถ้า x เป็น tuple และเราต้องการทำสิ่งนี้ x = ('a') def f (x): return {'a': x [0], 'b': x [1]}. get ( x [0], 9) สิ่งนี้จะเพิ่มดัชนีผิดพลาด
Idan Haim Shalom

2
@Idan: คำถามคือการทำซ้ำสวิตช์ ฉันแน่ใจว่าฉันสามารถทำลายรหัสนี้เช่นกันหากฉันพยายามใส่ค่าแปลก ๆ ใช่มันจะสร้างขึ้นมาใหม่ แต่มันก็ง่ายที่จะแก้ไข
Nick

394

ฉันชอบทำแบบนี้มาตลอด

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

จากที่นี่


16
วิธีการที่ดีบวกกับการได้รับ () เพื่อเริ่มต้นจับเป็นทางเลือกที่ดีที่สุดของฉันเกินไป
drAlberT

27
อาจไม่ใช่ความคิดที่ดีที่จะใช้แลมบ์ดาในกรณีนี้เพราะแลมบ์ดาถูกเรียกจริงทุกครั้งที่มีการสร้างพจนานุกรม
Asher

13
น่าเศร้าที่คนใกล้จะได้รับคือ วิธีการที่ใช้.get()(เช่นคำตอบที่สูงที่สุดในปัจจุบัน) จะต้องประเมินความเป็นไปได้ทั้งหมดก่อนที่จะส่งไปอย่างกระตือรือร้นและดังนั้นจึงไม่เพียง แต่เป็น (ไม่เพียง แต่มาก) ไม่มีประสิทธิภาพอย่างยิ่งและยังไม่มีผลข้างเคียง; คำตอบนี้ได้รับการแก้ไขปัญหาที่ แต่มีความละเอียดมากขึ้น ฉันจะใช้ถ้า / elif / อื่นและแม้แต่ใช้เวลานานในการเขียนเป็น 'กรณี'
ninjagecko

13
นี่จะไม่ประเมินฟังก์ชัน / lambdas ทั้งหมดทุกครั้งในทุกกรณีแม้ว่าจะส่งคืนผลลัพธ์อย่างใดอย่างหนึ่งเท่านั้น
slf

23
@slf ไม่มีเมื่อถึงการควบคุมการไหลที่ชิ้นส่วนของรหัสมันจะสร้าง 3 ฟังก์ชั่น (ผ่านการใช้ 3 lambdas) และแล้วสร้างพจนานุกรมกับผู้ที่ 3 ฟังก์ชั่นเป็นค่า แต่พวกเขายังคงไม่ได้ขอร้อง ( ประเมินอยู่ในคลุมเครือเล็กน้อย บริบทนั้น) ในตอนแรก จากนั้นพจนานุกรมจะได้รับการจัดทำดัชนีผ่าน[value]ซึ่งจะส่งกลับเฉพาะหนึ่งใน 3 ของฟังก์ชั่น (สมมติว่าvalueเป็นหนึ่งใน 3 ปุ่ม) ฟังก์ชั่นยังไม่ได้รับการเรียก ณ จุดนั้น จากนั้น(x)เรียกใช้ฟังก์ชันเพิ่งส่งคืนพร้อมด้วยxอาร์กิวเมนต์ (และผลลัพธ์จะเป็นresult) อีกสองฟังก์ชั่นจะไม่ถูกเรียกใช้
blubberdiblub

354

นอกจากวิธีการพจนานุกรม (ซึ่งฉันชอบ BTW) คุณยังสามารถใช้if- elif- elseเพื่อรับ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

แน่นอนว่านี่ไม่เหมือนกันกับการสลับ / ตัวพิมพ์ - คุณไม่สามารถล้มลงได้ง่ายเหมือนออกจากbreakคำสั่ง แต่คุณสามารถทดสอบที่ซับซ้อนมากขึ้น การจัดรูปแบบของมันนั้นดีกว่าชุดของifs ที่ซ้อนกันแม้ว่าจะเป็นหน้าที่ของมัน


51
ฉันต้องการสิ่งนี้จริง ๆ ใช้การสร้างภาษาแบบมาตรฐานและไม่โยน KeyError หากไม่พบเคสที่ตรงกับปัญหา
martyglaubitz

7
ฉันคิดถึงพจนานุกรม / getวิธี แต่วิธีมาตรฐานนั้นอ่านง่ายกว่า
Martin Thoma

2
@someuser แต่ข้อเท็จจริงที่ว่าพวกเขาสามารถ "ทับซ้อน" เป็นคุณลักษณะ คุณเพียงแค่ทำให้แน่ใจว่าลำดับความสำคัญที่ควรจะเกิดขึ้น สำหรับซ้ำ x: ทำx = the.other.thingก่อน โดยทั่วไปแล้วคุณจะมีซิงเกิ้ลถ้าหากมีเอลฟ์หลายตัวและอีกตัวหนึ่งเนื่องจากง่ายต่อการเข้าใจ
Matthew Schinckel

7
Nice, "Fall-through โดยไม่ใช้ elif" เป็นบิตสับสน แต่ แล้วเกี่ยวกับสิ่งนี้: ลืมเรื่อง "ล้มเหลว" และแค่ยอมรับมันในฐานะของสองคนif/elif/else?
Alois Mahdal

7
นอกจากนี้ยังมีมูลค่าการกล่าวขวัญเมื่อใช้สิ่งที่ต้องการx in 'bc'เก็บไว้ในใจว่าเป็น"" in "bc" True
Lohmar ASHAR

185

สูตร Python ที่ฉันโปรดปรานสำหรับการเปลี่ยน / ตัวเรือนคือ:

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

สั้นและง่ายสำหรับสถานการณ์ง่าย ๆ

เปรียบเทียบกับรหัส C 11 บรรทัด:

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

คุณสามารถกำหนดตัวแปรหลายตัวโดยใช้ tuples:

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

16
ฉันคิดว่านี่เป็นคำตอบที่มีประสิทธิภาพมากกว่าคำตอบที่ยอมรับ
cerd

3
ผู้ใช้ @some: C กำหนดให้ค่าส่งคืนเป็นชนิดเดียวกันสำหรับทุกกรณี Python ไม่ได้ ฉันต้องการเน้นถึงความยืดหยุ่นของ Python ในกรณีที่บางคนมีสถานการณ์ที่รับประกันการใช้งานดังกล่าว
ChaimG

3
@ ผู้ใช้บางคน: โดยส่วนตัวแล้วฉันพบว่า {} .get (,) อ่านได้ default = -1; result = choices.get(key, default)เพื่อให้สามารถอ่านเพิ่มเติมสำหรับผู้เริ่มต้นหลามคุณอาจต้องการที่จะใช้
ChaimG

4
เปรียบเทียบกับ 1 บรรทัดของ c ++result=key=='a'?1:key==b?2:-1
Jasen

4
@Jasen หนึ่งสามารถยืนยันว่าคุณสามารถทำได้ในหนึ่งบรรทัดของ Python เช่นกัน: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). แต่สายการบินหนึ่งสามารถอ่านได้?
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))

การใช้งาน:

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

แบบทดสอบ:

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

64
นี่ไม่ใช่ภัยคุกคามที่ปลอดภัย หากมีการกดสวิตช์หลายครั้งพร้อมกันสวิตช์ทั้งหมดจะรับค่าสวิตช์สุดท้าย
francescortiz

48
แม้ว่า @francescortiz อาจหมายถึงเธรดที่ปลอดภัย แต่ก็ไม่ได้ปลอดภัย มันคุกคามค่าของตัวแปร!
Zizouz212

7
ปัญหาความปลอดภัยหัวข้อแนวโน้มที่อาจจะทำงานรอบโดยใช้การจัดเก็บด้ายท้องถิ่น หรือหลีกเลี่ยงได้ทั้งหมดโดยส่งคืนอินสแตนซ์และใช้อินสแตนซ์นั้นสำหรับการเปรียบเทียบเคส
blubberdiblub

6
@blubberdiblub แต่ถ้าอย่างนั้นมันมีประสิทธิภาพมากกว่าที่จะใช้ifคำสั่งมาตรฐานหรือไม่?
wizzwizz4

9
สิ่งนี้ยังไม่ปลอดภัยหากใช้ในหลายฟังก์ชั่น ในตัวอย่างที่ให้ไว้ถ้าcase(2)บล็อกที่เรียกว่าฟังก์ชั่นอื่นที่ใช้สวิตช์ () จากนั้นเมื่อทำcase(2, 3, 5, 7)ฯลฯ เพื่อค้นหากรณีถัดไปที่จะดำเนินการก็จะใช้ค่าสวิตช์ที่กำหนดโดยฟังก์ชั่นอื่น ๆ ไม่ได้เป็นหนึ่ง .
user9876

52

หนึ่งที่ชื่นชอบของฉันเป็นดีจริงๆสูตร คุณจะชอบมันมาก มันเป็นคำที่ใกล้เคียงที่สุดที่ฉันเคยเห็นจริง ๆ แล้วโดยเฉพาะในคุณสมบัติ

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

นี่คือตัวอย่าง:

# 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
ฉันจะแทนที่for case in switch()ด้วยwith switch() as caseทำให้รู้สึกมากขึ้นเพราะมันต้องทำงานเพียงครั้งเดียว
สกี

4
@Sirmirmas: โปรดทราบว่าwithไม่อนุญาตให้ทำbreakเช่นนั้นดังนั้นตัวเลือกการผิดพลาดจะถูกนำออกไป
Jonas Schäfer

5
ขออภัยที่ไม่ใช้ความพยายามมากขึ้นในการพิจารณาตนเอง: คำตอบที่คล้ายกันข้างต้นไม่ปลอดภัยสำหรับเธรด นี่คือ?
David Winiecki

1
@DavidWiniecki ส่วนประกอบของโค้ดที่หายไปจากด้านบน (และอาจเป็นลิขสิทธิ์จากการเปิดใช้งาน) ดูเหมือนจะปลอดภัยในเธรด
Jasen

รุ่นนี้จะเป็นอะไรอีก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
การใช้ผู้จัดการบริบทเป็นโซลูชันที่สร้างสรรค์ดี ผมอยากแนะนำให้เพิ่มบิตของคำอธิบายและอาจจะเชื่อมโยงไปยังข้อมูลบางส่วนในบริบทผู้จัดการที่จะให้โพสต์นี้บางดีบริบท;)
Will

2
ฉันไม่ชอบ if / elif chains มาก แต่นี่เป็นทั้งความคิดสร้างสรรค์และวิธีแก้ปัญหาทั้งหมดที่ฉันเห็นโดยใช้ไวยากรณ์ที่มีอยู่ของ Python
itsbruce

2
นี่เป็นสิ่งที่ดีจริงๆ การปรับปรุงที่แนะนำอย่างหนึ่งคือการเพิ่มคุณสมบัติ (สาธารณะ) valueให้กับคลาส Switch เพื่อให้คุณสามารถอ้างอิงcase.valueคำสั่งภายใน
ปีเตอร์

48

มีรูปแบบที่ฉันเรียนรู้จากรหัส 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'

คุณสามารถใช้งานได้ทุกเมื่อที่คุณต้องการส่งไปยังโทเค็นและเรียกใช้โค้ดเพิ่มเติม ในเครื่องของรัฐที่คุณจะมีวิธีการและจัดส่งได้ที่state_ self.stateสวิตช์นี้สามารถขยายได้อย่างหมดจดโดยสืบทอดจากคลาสฐานและกำหนดdo_วิธีการของคุณเอง บ่อยครั้งที่คุณไม่มีdo_วิธีการในคลาสพื้นฐาน

แก้ไข: ใช้งานอย่างไร

ในกรณีของ SMTP คุณจะได้รับHELOจากสาย รหัสที่เกี่ยวข้อง (จากtwisted/mail/smtp.pyปรับเปลี่ยนสำหรับกรณีของเรา) มีลักษณะเช่นนี้

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

คุณจะได้รับ' HELO foo.bar.com '(หรือคุณอาจได้รับ'QUIT'หรือ'RCPT TO: foo') นี้จะ tokenized เข้าเป็นparts ชื่อวิธีการค้นหาที่เกิดขึ้นจริงจะนำมาจาก['HELO', 'foo.bar.com']parts[0]

(วิธีการดั้งเดิมเรียกอีกอย่างว่าstate_COMMANDเพราะมันใช้รูปแบบเดียวกันในการใช้เครื่องสถานะเช่นgetattr(self, 'state_' + self.mode))


4
ฉันไม่เห็นประโยชน์จากรูปแบบนี้เพียงแค่เรียกเมธอดโดยตรง: SMTP (). do_HELO ('foo.bar.com') ตกลงคุณสามารถใช้รหัสทั่วไปใน lookupMethod ได้ แต่เนื่องจากสามารถเขียนทับได้ด้วย คลาสย่อยที่ฉันไม่เห็นสิ่งที่คุณได้รับจากทางอ้อม
Mr Shark

1
คุณจะไม่รู้วิธีการโทรล่วงหน้านั่นคือการพูดว่า 'HELO' มาจากตัวแปร ฉันได้เพิ่มตัวอย่างการใช้งานให้กับโพสต์ต้นฉบับ

ฉันขอแนะนำได้ง่ายๆว่า: eval ('SMTP (). do_' + command) ('foo.bar.com')
jforberg

8
EVAL? อย่างจริงจัง? และแทนที่จะใช้วิธีหนึ่งทันทีในการโทรเราสามารถสร้างอินสแตนซ์ได้หนึ่งครั้งและใช้ในทุกการโทรหากไม่มีสถานะภายใน
Mahesh

1
IMO คีย์จริงที่นี่คือการส่งโดยใช้ getattr เพื่อระบุฟังก์ชั่นที่จะเรียกใช้ หากวิธีการอยู่ในโมดูลคุณสามารถทำ getattr (locals (), func_name) เพื่อรับมัน ส่วน 'do_' นั้นดีต่อความปลอดภัย / ข้อผิดพลาดดังนั้นสามารถเรียก funcs ที่มีคำนำหน้าเท่านั้น SMTP เองเรียกการค้นหาวิธี โดยทั่วไปแล้วภายนอกจะไม่รู้เกี่ยวกับสิ่งนี้ มันไม่สมเหตุสมผลเลยที่จะทำ SMTP (). lookupMethod (ชื่อ) (ข้อมูล) เนื่องจากคำสั่งและข้อมูลอยู่ในสตริงเดียวและ SMTP แยกวิเคราะห์จึงเหมาะสมกว่า สุดท้าย SMTP อาจมีสถานะที่ใช้ร่วมกันอื่นซึ่งแสดงว่าเป็นคลาส
ShawnFumo

27

สมมติว่าคุณไม่ต้องการแค่คืนค่า แต่ต้องการใช้วิธีการที่เปลี่ยนแปลงบางสิ่งบนวัตถุ ใช้วิธีการที่ระบุไว้ที่นี่จะเป็น:

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

สิ่งที่เกิดขึ้นที่นี่คือหลามประเมินวิธีการทั้งหมดในพจนานุกรม ดังนั้นแม้ว่าค่าของคุณคือ 'a' วัตถุจะเพิ่มขึ้นและลดลงตาม x

สารละลาย:

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

result = func(*args)

ดังนั้นคุณจะได้รับรายการที่มีฟังก์ชั่นและข้อโต้แย้ง ด้วยวิธีนี้เฉพาะตัวชี้ฟังก์ชั่นและรายการอาร์กิวเมนต์ที่ได้รับกลับมาไม่ได้รับการประเมิน 'ผลลัพธ์' จากนั้นประเมินการเรียกฟังก์ชันที่ส่งคืน


23

ฉันแค่จะลดลงสองเซ็นต์ของฉันที่นี่ เหตุผลที่ไม่มีคำสั่ง case / switch ใน Python นั้นเป็นเพราะ Python ปฏิบัติตามหลักการของ 'Theres ทางเดียวเท่านั้นที่จะทำอะไร' เห็นได้ชัดว่าคุณสามารถสร้างฟังก์ชันสวิตช์ / เคสได้หลายวิธี แต่วิธี Pythonic ในการทำสิ่งนี้ให้สำเร็จคือโครงสร้าง if / elif กล่าวคือ

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

ฉันแค่รู้สึกว่า PEP 8 สมควรได้รับการพยักหน้าที่นี่ หนึ่งในสิ่งที่สวยงามเกี่ยวกับ Python คือความเรียบง่ายและสง่างาม ส่วนใหญ่มาจากหลักการที่วางไว้ใน PEP 8 ของเรารวมถึง "มีทางเดียวเท่านั้นที่จะทำอะไรบางอย่าง"


6
แล้วทำไมไพ ธ อนถึงมีลูปและในขณะที่ลูปล่ะ ทุกสิ่งที่คุณสามารถทำได้ด้วย for for loop คุณสามารถนำไปใช้กับ a loop ได้
itsbruce

1
จริง Switch / case ถูกใช้บ่อยเกินไปโดยการเริ่มต้นโปรแกรมเมอร์ สิ่งที่พวกเขาต้องการจริงๆเป็นรูปแบบกลยุทธ์
user228395

ดูเหมือนว่า Python จะเป็น Clojure
TWR Cole

1
@ TWCole ฉันไม่คิดอย่างนั้น Python กำลังทำมันก่อน Python ได้รับรอบตั้งแต่ปี 1990 และ Clojure ตั้งแต่ปี 2007
Taylor

มีทางเดียวเท่านั้นที่จะทำอะไรบางอย่าง Python 2.7 หรือ Python 3 ฮ่า ๆ.
TWR Cole

17

ขยายความคิด "dict as switch" หากคุณต้องการใช้ค่าเริ่มต้นสำหรับสวิตช์ของคุณ:

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

14
ฉันคิดว่ามันชัดเจนกว่าที่จะใช้. get () บน dict ตามค่าเริ่มต้นที่ระบุ ฉันชอบที่จะออกจากข้อยกเว้นสำหรับสถานการณ์พิเศษและมันตัดโค้ดสามบรรทัดและระดับของการเยื้องโดยไม่คลุมเครือ
Chris B.

10
นี่เป็นกรณีพิเศษ มันอาจจะใช่หรือไม่ใช่สถานการณ์ที่หายากขึ้นอยู่กับว่ามีประโยชน์ แต่มันก็เป็นข้อยกเว้น (ถอยกลับ'default') จากกฎ (รับบางอย่างจากคำสั่งนี้) จากการออกแบบโปรแกรม Python ใช้ข้อยกเว้นที่ส่วนท้ายของหมวก ที่ถูกกล่าวว่าใช้getอาจทำให้รหัสบิตดีกว่า
Mike Graham

16

หากคุณมีบล็อกตัวพิมพ์เล็กและใหญ่คุณสามารถลองใช้ฟังก์ชั่นตารางค้นหาพจนานุกรม ...

หากคุณยังไม่ได้ทำสิ่งนี้มาก่อนขอแนะนำให้คุณเข้าสู่โปรแกรมดีบั๊กและดูว่าพจนานุกรมค้นหาแต่ละฟังก์ชั่นอย่างไร

หมายเหตุ: ทำไม่ได้ใช้ "()" ภายในกรณี / พจนานุกรมค้นหาหรือมันจะโทรหากันของฟังก์ชั่นของคุณเป็นบล็อกพจนานุกรม / กรณีที่ถูกสร้างขึ้น จดจำสิ่งนี้เพราะคุณต้องการโทรแต่ละฟังก์ชั่นเมื่อใช้การค้นหาสไตล์แฮช

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

ฉันชอบทางออกของคุณ แต่ถ้าฉันต้องผ่านตัวแปรหรือวัตถุบางอย่าง
Tedo Vrbanec

สิ่งนี้จะไม่ทำงานหากวิธีการคาดหวังว่าพารามิเตอร์
Kulasangar

16

หากคุณกำลังค้นหาคำสั่งพิเศษเป็น "สวิตช์" ฉันสร้างโมดูลหลามที่ขยาย Python มันเรียกว่าESPYเป็น "Enhanced Structure for Python" และมีให้สำหรับทั้ง Python 2.x และ Python 3.x

ตัวอย่างเช่นในกรณีนี้คำสั่ง switch สามารถทำได้โดยรหัสต่อไปนี้:

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

ที่สามารถใช้แบบนี้:

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

ดังนั้น espy แปลมันใน Python เป็น:

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

เจ๋งมาก แต่อะไรคือจุดwhile True:สูงสุดของรหัส Python ที่สร้างขึ้น มันจะกดปุ่มbreakที่ด้านล่างสุดของรหัส Python ที่สร้างขึ้นอย่างหลีกเลี่ยงไม่ได้ดังนั้นฉันจึงเห็นว่าทั้งสองwhile True:และbreakสามารถลบออกได้ นอกจากนี้ ESPY ฉลาดพอที่จะเปลี่ยนชื่อของcontผู้ใช้ชื่อเดียวกันในรหัสของตัวเองหรือไม่? ไม่ว่าในกรณีใดฉันต้องการใช้วานิลลา Python ดังนั้นฉันจะไม่ใช้มัน แต่ก็เจ๋งไม่น้อยเลย +1 เพื่อความเยือกเย็น
ArtOfWarfare

@ArtOfWarfare เหตุผลสำหรับwhile True:และbreaks คืออนุญาต แต่ไม่จำเป็นต้องล้มลง
โซโลมอน Ucko

โมดูลนี้ยังใช้งานได้หรือไม่
โซโลมอน Ucko

15

ฉันพบว่าโครงสร้างสวิตช์ทั่วไป:

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

สามารถแสดงใน Python ดังต่อไปนี้:

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

หรือจัดรูปแบบด้วยวิธีที่ชัดเจนยิ่งขึ้น:

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

แทนที่จะเป็นคำสั่งเวอร์ชันไพ ธ อนเป็นนิพจน์ซึ่งประเมินค่า


นอกจากนี้แทนที่จะเป็น ... พารามิเตอร์ ... และ p1 (x) วิธีการเกี่ยวกับparameterและp1==parameter
Bob Stein

@ BobStein-VisiBone f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'สวัสดีนี่เป็นตัวอย่างที่วิ่งในเซสชั่นหลามของฉัน: เมื่อฉันภายหลังเรียกว่าf(2)ผมได้'c'; f(1), 'b'; และ,f(0) 'a'สำหรับ p1 (x) มันหมายถึงภาคแสดง; ตราบใดที่มันส่งคืนTrueหรือFalseไม่ว่าจะเป็นการเรียกใช้ฟังก์ชันหรือการแสดงออกก็ไม่เป็นไร
leo

@ BobStein-VisiBone ใช่คุณพูดถูก! ขอบคุณ :) เพื่อให้การแสดงออกหลายบรรทัดทำงานได้ควรใส่วงเล็บไว้ในคำแนะนำของคุณหรือตามตัวอย่างที่แก้ไข
leo

ยอดเยี่ยม ตอนนี้ฉันจะลบความคิดเห็นทั้งหมดของฉันเกี่ยวกับ parens
Bob Stein

15

คำตอบส่วนใหญ่ที่นี่ค่อนข้างเก่าและโดยเฉพาะคำตอบที่ได้รับการยอมรับดังนั้นจึงควรปรับปรุง

ข้อแรกคำถามที่พบบ่อยของ Pythonอย่างเป็นทางการครอบคลุมเรื่องนี้และแนะนำelifโซ่สำหรับกรณีง่าย ๆ และdictสำหรับกรณีที่มีขนาดใหญ่ขึ้นหรือซับซ้อนมากขึ้น นอกจากนี้ยังแนะนำชุดของvisit_เมธอด (สไตล์ที่ใช้โดยเฟรมเวิร์กเซิร์ฟเวอร์จำนวนมาก) สำหรับบางกรณี:

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

คำถามที่พบบ่อยยังกล่าวถึงPEP 275ซึ่งเขียนขึ้นเพื่อรับการตัดสินใจอย่างเป็นทางการครั้งเดียวและทั้งหมดสำหรับการเพิ่มคำสั่งสวิตช์แบบ C แต่ PEP ที่รอการตัดบัญชีจริงหลาม 3 และมันก็ถูกปฏิเสธอย่างเป็นทางการเท่านั้นเป็นข้อเสนอที่แยกจากกันPEP 3103 แน่นอนว่าคำตอบคือไม่ - แต่ PEP ทั้งสองมีลิงก์ไปยังข้อมูลเพิ่มเติมหากคุณสนใจในเหตุผลหรือประวัติ


สิ่งหนึ่งที่เกิดขึ้นหลายครั้ง (และสามารถเห็นได้ใน PEP 275 แม้ว่ามันจะถูกตัดออกไปตามคำแนะนำจริง) ก็คือถ้าคุณใส่ใจด้วยการใช้รหัส 8 บรรทัดเพื่อจัดการกับ 4 คดีและ 6 ข้อ บรรทัดที่คุณมีใน C หรือ Bash คุณสามารถเขียนสิ่งนี้ได้เสมอ:

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

สิ่งนี้ไม่ได้รับการสนับสนุนอย่างแน่นอนจาก PEP 8 แต่สามารถอ่านได้และไม่ผิดเพี้ยนไป


กว่าทศวรรษที่ผ่านมานับตั้งแต่ PEP 3103 ถูกปฏิเสธปัญหาของคำแถลงกรณี C-style หรือแม้แต่เวอร์ชั่นที่ทรงพลังกว่าเล็กน้อยใน Go ก็ถือว่าตายแล้ว เมื่อใดก็ตามที่มีคนนำมันขึ้นมาจาก python-ideas หรือ -dev พวกเขาจะถูกอ้างถึงในการตัดสินใจแบบเก่า

อย่างไรก็ตามแนวคิดของการจับคู่รูปแบบ ML เต็มรูปแบบเกิดขึ้นทุก ๆ สองสามปีโดยเฉพาะอย่างยิ่งเมื่อภาษาอย่าง Swift และ Rust ได้นำมาใช้ ปัญหาคือมันยากที่จะได้รับประโยชน์จากการจับคู่รูปแบบโดยไม่ต้องใช้ข้อมูลประเภทพีชคณิต ในขณะที่ Guido เห็นอกเห็นใจต่อความคิด แต่ก็ไม่มีใครคิดข้อเสนอที่เข้ากับ Python ได้ดีนัก (คุณสามารถอ่านตัวอย่างชาวฟางปี 2014 ของฉัน ) สิ่งนี้อาจเปลี่ยนแปลงได้dataclassใน 3.7 และข้อเสนอประปรายสำหรับผู้ที่มีประสิทธิภาพมากขึ้นenumในการจัดการประเภทผลรวมหรือข้อเสนอที่หลากหลายสำหรับการผูกคำแถลงในท้องถิ่นที่แตกต่างกัน (เช่นPEP 3150หรือ ชุดของข้อเสนอที่กำลังคุยกันเรื่อง -ideas) แต่จนถึงตอนนี้ก็ยังไม่ได้

นอกจากนี้ยังมีข้อเสนอสำหรับการจับคู่ Perl 6 สไตล์เป็นบางครั้งซึ่งโดยทั่วไปแล้วเป็นข้อผิดพลาดของทุกสิ่งตั้งแต่elifการ regex ไปจนถึงการสับเปลี่ยนชนิดการแจกจ่ายเดี่ยว


15

โซลูชันเพื่อเรียกใช้ฟังก์ชัน:

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

โดยที่ foo1 (), foo2 (), foo3 () และ default () เป็นฟังก์ชั่น


1
ใช่เช่นถ้าตัวแปรตัวเลือกของคุณ == "case2" ผลลัพธ์ของคุณ = foo2 ()
Alejandro Quintanar

และอื่น ๆ
Alejandro Quintanar

ใช่ฉันเข้าใจจุดประสงค์ แต่ความกังวลของฉันคือถ้าคุณต้องการเพียงแค่foo2(), the foo1(), foo3()และdefault()ฟังก์ชั่นทั้งหมดก็จะทำงานได้ซึ่งหมายความว่าสิ่งต่าง ๆ อาจใช้เวลานาน
Brian Underwood

1
ละเว้น () ภายในพจนานุกรม get(option)()ใช้ แก้ไขปัญหา.
timgeb

1
ที่ดีเยี่ยมการใช้ () เป็นวิธีการแก้ตะแกรงผมทำสรุปสาระสำคัญในการทดสอบมันgist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Alejandro Quintanar

13

ฉันไม่พบคำตอบง่ายๆที่ฉันค้นหาทุกที่ในการค้นหาของ Google แต่ฉันก็คิดออกอยู่ดี มันค่อนข้างง่ายจริงๆ ตัดสินใจที่จะโพสต์และอาจป้องกันรอยขีดข่วนน้อยบนหัวของคนอื่น กุญแจสำคัญคือ "ใน" และสิ่งอันดับ นี่คือพฤติกรรมของคำสั่ง switch ที่มีการเลื่อนผ่านรวมถึง RANDOM fall-through

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

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

print()

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

ให้:

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.

ข้อผิดพลาดอยู่ตรงนี้ที่ไหน?
Jonas Schäfer

อ๊ะ! เกิดขึ้นที่นั่น แต่ฉันไม่ได้มีส่วนร่วมกับ Stack Overflow อีกต่อไป ไม่ชอบพวกเขาเลย ฉันชอบผลงานของผู้อื่น แต่ไม่ใช่ Stackoverflow หากคุณใช้การผ่านไปเพื่อการทำงานแล้วคุณต้องการที่จะจับเงื่อนไขบางอย่างในคำสั่งกรณีเดียวในสวิตช์ (จับทั้งหมด) จนกว่าคุณจะถึงคำสั่งหยุดพักในสวิตช์
JD Graham

2
ที่นี่ทั้งค่า "Dog" และ "Cat" FALL THROUGH และจัดการโดยฟังก์ชั่น SAME ซึ่งพวกมันถูกกำหนดว่ามี "สี่ขา" มันเป็นนามธรรมที่เทียบเท่ากับการผ่านและค่าที่แตกต่างกันจัดการโดยคำสั่งกรณีเดียวกันที่หยุดพักเกิดขึ้น
JD Graham

@JDGraham ผมคิดว่าโจนัสหมายถึงทุกแง่มุมของ fallthrough อื่นซึ่งเกิดขึ้นเมื่อโปรแกรมเมอร์บางครั้งลืมที่จะเขียนในตอนท้ายของรหัสสำหรับการbreak caseแต่ผมคิดว่าเราไม่จำเป็นต้องเช่น "fallthrough" :)
มิคาอิล Batcer

12

โซลูชันที่ฉันใช้:

การรวมกันของ 2 โซลูชั่นที่โพสต์ที่นี่ซึ่งค่อนข้างง่ายต่อการอ่านและรองรับค่าเริ่มต้น

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

ที่ไหน

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

ค้นหา"lambda x: x - 2"ใน dict และใช้กับx=23

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

ไม่พบมันใน Dict และใช้ค่าเริ่มต้นด้วย"lambda x: x - 22"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

พิจารณารวมถึงคำอธิบายสั้น ๆ เกี่ยวกับรหัสของคุณและวิธีแก้ปัญหาที่โพสต์ไว้
Henry Woody

ตกลงฉันได้เพิ่มความคิดเห็นไว้สำหรับตอนนี้
Vikhyat Agarwal

8

ฉันชอบคำตอบของ Mark Bies

เนื่องจากxตัวแปรต้องใช้สองครั้งฉันจึงแก้ไขฟังก์ชั่นแลมบ์ดาให้เป็นแบบไร้พารามิเตอร์

ฉันต้องวิ่งด้วย 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'

แก้ไข:ฉันสังเกตเห็นว่าฉันสามารถใช้Noneประเภทกับพจนานุกรม ดังนั้นนี่จะเลียนแบบswitch ; case else


ไม่ใช่กรณีที่เลียนแบบได้ง่าย ๆresult[None]()?
Bob Stein

ใช่แล้ว ฉันหมายถึงresult = {'a': 100, None:5000}; result[None]
guneysus

4
เพียงแค่ตรวจสอบว่าไม่มีใครคิดว่าNone:มีพฤติกรรมเช่นdefault:นั้น
Bob Stein

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

สั้นและง่ายต่อการอ่านมีค่าเริ่มต้นและสนับสนุนการแสดงออกทั้งในเงื่อนไขและค่าส่งคืน

อย่างไรก็ตามมีประสิทธิภาพน้อยกว่าโซลูชันด้วยพจนานุกรม ตัวอย่างเช่น Python ต้องสแกนเงื่อนไขทั้งหมดก่อนส่งคืนค่าเริ่มต้น


7

คุณสามารถใช้ทictที่ได้รับการจัดส่ง:

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

เอาท์พุท:

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

6

ง่ายไม่ทดสอบ แต่ละเงื่อนไขถูกประเมินโดยอิสระ: ไม่มี fall-through แต่ทุกกรณีจะถูกประเมิน (แม้ว่านิพจน์เพื่อเปิดใช้จะถูกประเมินเพียงครั้งเดียวเท่านั้น) เว้นแต่ว่าจะมีคำสั่ง break ตัวอย่างเช่น,

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

พิมพ์Was 1. Was 1 or 2. Was something. (Dammit! ทำไมฉันไม่สามารถมีช่องว่างต่อท้ายในบล็อกรหัสแบบอินไลน์?)ถ้าexpressionประเมิน1, Was 2.ถ้าexpressionประเมิน2หรือWas something.ถ้าexpressionประเมินไปเป็นอย่างอื่น


1
การล่มสลายของงานทำได้ดีเพียง แต่ไปที่ do_default
syockit

5

การกำหนด:

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

อนุญาตให้คุณใช้ไวยากรณ์ที่ค่อนข้างตรงไปตรงมากับกรณีที่รวมอยู่ในแผนที่:

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

ฉันพยายามเปลี่ยนสวิตช์ใหม่ในวิธีที่จะให้ฉันกำจัด "แลมบ์ดา": แต่ก็ยอมแพ้ การปรับแต่งนิยาม:

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

อนุญาตให้ฉันจับคู่หลายกรณีกับรหัสเดียวกันและระบุตัวเลือกเริ่มต้น:

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

แต่ละกรณีที่ทำซ้ำจะต้องอยู่ในพจนานุกรมของตัวเอง switch () รวมพจนานุกรมก่อนค้นหาค่า มันยังน่าเกลียดกว่าที่ฉันต้องการ แต่มันมีประสิทธิภาพขั้นพื้นฐานในการใช้การค้นหาแบบแฮชบนนิพจน์แทนที่จะวนผ่านคีย์ทั้งหมด


5

ผมคิดว่าวิธีที่ดีที่สุดคือการใช้สำนวนภาษาหลามเพื่อให้ทดสอบรหัสของคุณ ดังที่แสดงในคำตอบก่อนหน้านี้ฉันใช้พจนานุกรมเพื่อใช้ประโยชน์จากโครงสร้างและภาษาของหลามและเก็บรหัส "เคส" ไว้ในวิธีการต่าง ๆ ด้านล่างมีคลาส แต่คุณสามารถใช้โมดูลโดยตรงและฟังก์ชันต่างๆได้โดยตรง ชั้นเรียนมีวิธีการที่สามารถทดสอบแยกได้ ขึ้นอยู่กับความต้องการของคุณคุณสามารถเล่นกับวิธีการคงที่และคุณลักษณะเช่นกัน

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)

เป็นไปได้ที่จะใช้ประโยชน์จากวิธีนี้โดยใช้คลาสเป็นคีย์ของ "__choice_table" ด้วยวิธีนี้คุณสามารถหลีกเลี่ยงการใช้งานในทางที่ผิดได้และรักษาความสะอาดและสามารถทดสอบได้

สมมติว่าคุณต้องประมวลผลข้อความหรือแพ็คเก็ตจำนวนมากจากเน็ตหรือ MQ ของคุณ แพ็คเก็ตทุกอันมีโครงสร้างของตัวเองและรหัสการจัดการ (ในลักษณะทั่วไป) ด้วยโค้ดด้านบนมันเป็นไปได้ที่จะทำสิ่งนี้:

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)

ดังนั้นความซับซ้อนไม่ได้แพร่กระจายในการไหลเวียนของรหัส แต่ก็มีการแสดงในโครงสร้างรหัส


น่าเกลียดจริงๆ ... กล่องสวิทช์นั้นสะอาดมากเมื่ออ่าน ไม่สามารถเข้าใจได้ว่าเพราะเหตุใดจึงไม่มีการใช้งานใน Python
jmcollin92

@AndyClifton: ฉันขอโทษ ... เป็นตัวอย่าง? นึกถึงทุกครั้งที่คุณจำเป็นต้องมีรหัสการตัดสินใจหลายสาขาและคุณสามารถใช้วิธีนี้ได้
J_Zar

@ jmcollin92: คำสั่ง switch นั้นสะดวกสบายฉันเห็นด้วย อย่างไรก็ตามโปรแกรมเมอร์มักจะเขียนคำสั่งที่ยาวมากและรหัสซึ่งไม่สามารถใช้ซ้ำได้ วิธีที่ฉันอธิบายไว้นั้นสะอาดกว่าการทดสอบและนำกลับมาใช้ใหม่ได้มากขึ้น IMHO
J_Zar

@J_Zar: อีกครั้ง ตัวอย่างคำขอของฉัน: ใช่ฉันเข้าใจแล้ว แต่ฉันพยายามดิ้นรนเพื่อให้เข้ากับบริบทของโค้ดขนาดใหญ่กว่านี้ คุณช่วยแสดงให้ฉันเห็นว่าฉันจะใช้สิ่งนี้ในสถานการณ์จริงได้อย่างไร
Andy Clifton

1
@AndyClifton: ฉันขอโทษฉันมาสาย แต่ฉันโพสต์ตัวอย่างกรณี
J_Zar

5

ขยายคำตอบของ Greg Hewgill - เราสามารถแค็ปซูลโซลูชันพจนานุกรมโดยใช้มัณฑนากร:

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

ซึ่งสามารถใช้กับ@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

ข่าวดีก็คือสิ่งนี้ได้ทำไปแล้วในNeoPySwitch -module เพียงติดตั้งโดยใช้ pip:

pip install NeoPySwitch

5

ทางออกที่ฉันมักจะใช้ซึ่งทำให้การใช้พจนานุกรมคือ:

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

นี่เป็นข้อได้เปรียบที่ไม่ได้พยายามประเมินฟังก์ชั่นทุกครั้งและคุณเพียงแค่ต้องให้แน่ใจว่าฟังก์ชั่นด้านนอกได้รับข้อมูลทั้งหมดที่ฟังก์ชั่นด้านในต้องการ


5

มีคำตอบมากมายที่กล่าวไว้ว่า "เราไม่มีสวิตช์ใน Python ทำอย่างนี้" อย่างไรก็ตามฉันอยากจะชี้ให้เห็นว่าคำสั่งเปลี่ยนเองเป็นโครงสร้างที่ถูกทารุณอย่างง่ายที่สามารถและควรหลีกเลี่ยงในกรณีส่วนใหญ่เพราะพวกเขาส่งเสริมการเขียนโปรแกรมขี้เกียจ กรณีในจุด:

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

ตอนนี้คุณสามารถทำสิ่งนี้ได้ด้วย switch-statement (ถ้า Python เสนอให้) แต่คุณจะเสียเวลาเพราะมีวิธีการที่ทำได้ดี หรือบางทีคุณมีสิ่งที่ชัดเจนน้อยกว่า:

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

อย่างไรก็ตามการดำเนินการประเภทนี้สามารถและควรจัดการกับพจนานุกรมเพราะจะเร็วขึ้นซับซ้อนน้อยกว่ามีแนวโน้มที่จะเกิดข้อผิดพลาดและกะทัดรัดน้อยลง

และส่วนใหญ่ของ "กรณีการใช้งาน" สำหรับงบสวิตช์จะตกอยู่ในหนึ่งในสองกรณีนี้ มีเหตุผลเพียงเล็กน้อยที่จะใช้หากคุณคิดถึงปัญหาของคุณอย่างถี่ถ้วน

ดังนั้นแทนที่จะถามว่า "ฉันจะสลับใน Python ได้อย่างไร" บางทีเราควรถามว่า "ทำไมฉันถึงต้องการสลับใน Python" เพราะนั่นเป็นคำถามที่น่าสนใจและมักจะเปิดเผยข้อบกพร่องในการออกแบบสิ่งที่คุณกำลังสร้าง

ทีนี้ก็ไม่ได้หมายความว่าสวิทช์ไม่ควรใช้ เครื่องจักรรัฐ lexers parsers และออโตมาตาใช้ในระดับหนึ่งและโดยทั่วไปเมื่อคุณเริ่มจากอินพุตแบบสมมาตรและไปที่เอาต์พุตแบบอสมมาตรจะมีประโยชน์ คุณเพียงแค่ต้องแน่ใจว่าคุณไม่ได้ใช้สวิตช์เป็นค้อนเพราะคุณเห็นเล็บจำนวนหนึ่งในรหัสของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.