Câu hỏi Thay thế cho câu lệnh switch trong Python?


Tôi muốn viết một hàm bằng Python trả về các giá trị cố định khác nhau dựa trên giá trị của một chỉ mục đầu vào.

Trong các ngôn ngữ khác, tôi sẽ sử dụng switch hoặc là case tuyên bố, nhưng Python dường như không có switch tuyên bố. Các giải pháp Python được đề xuất trong trường hợp này là gì?


1445
2017-09-13 00:36


gốc


PEP liên quan, do chính Guido sáng tác: PEP 3103 - chb
@chb Trong đó PEP, Guido không đề cập rằng nếu / elif chuỗi cũng là một nguồn cổ điển của lỗi. Đó là một cấu trúc rất dễ vỡ. - itsbruce
Thiếu từ tất cả các giải pháp ở đây là phát hiện giá trị trường hợp trùng lặp. Là nguyên tắc không nhanh, điều này có thể là mất mát quan trọng hơn hiệu suất hoặc tính năng tuyệt đối. - Bob Stein
Tất cả các giải pháp được cung cấp thực sự xấu xí ... trường hợp chuyển đổi rất sạch sẽ khi đọc. Không thể hiểu tại sao nó không được thực hiện bằng Python. - jmcollin92
switch thực sự là "linh hoạt" hơn cái gì đó trả về các giá trị cố định khác nhau dựa trên giá trị của chỉ mục đầu vào. Nó cho phép các phần mã khác nhau được thực hiện. Nó thực sự thậm chí không cần phải trả về một giá trị. Tôi tự hỏi nếu một số câu trả lời ở đây là thay thế tốt cho một vị tướng switchtuyên bố, hoặc chỉ cho trường hợp trả về các giá trị không có khả năng thực hiện các đoạn mã chung. - sancho.s


Các câu trả lời:


Bạn có thể sử dụng từ điển:

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

1128
2017-09-13 00:38



Điều gì xảy ra nếu không tìm thấy x? - Nick
@nick: bạn có thể sử dụng defaultdict - Eli Bendersky
Đây không phải là một chuyển đổi / trường hợp thực sự ... vui lòng xem câu trả lời của tôi bên dưới - daniel
Tôi khuyên bạn nên đặt dict bên ngoài của hàm nếu hiệu suất là một vấn đề, do đó, nó không xây dựng lại dict trên mọi cuộc gọi hàm - Claudiu
@EliBendersky, Sử dụng get phương pháp có lẽ sẽ bình thường hơn bằng cách sử dụng collections.defaultdict trong trường hợp này. - Mike Graham


Nếu bạn muốn mặc định, bạn có thể sử dụng từ điển get(key[, default]) phương pháp:

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

1110
2017-09-19 15:45



Nếu 'a' và 'b' khớp 1, và 'c' và 'd' khớp với 2 thì sao? - John Mee
@ JM: Vâng, rõ ràng là tra cứu từ điển không hỗ trợ thu gọn. Bạn có thể thực hiện tra cứu từ điển kép. I E. 'a' & 'b' trỏ đến answer1 và 'c' và 'd' trỏ tới answer2, được chứa trong từ điển thứ hai. - Nick
điều này tốt hơn để chuyển một giá trị mặc định - HaTiMSuM
Có một vấn đề với cách tiếp cận này, đầu tiên mỗi khi bạn gọi f bạn sẽ tạo ra dict một lần nữa thứ hai nếu bạn có giá trị phức tạp hơn, bạn có thể nhận được một exceptions ex. nếu x là một tuple và chúng ta muốn làm một cái gì đó như thế này x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Điều này sẽ làm tăng IndexError - Idan Haim Shalom
@Idan: Câu hỏi là để nhân rộng chuyển đổi. Tôi chắc chắn tôi có thể phá vỡ mã này quá nếu tôi đã cố gắng đưa vào các giá trị kỳ quặc. Có, nó sẽ tái tạo, nhưng nó rất đơn giản để sửa chữa. - Nick


Tôi luôn thích làm theo cách này

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

Từ đây


294
2017-09-13 00:41



phương pháp tuyệt vời, kết hợp với get () để xử lý mặc định cũng là lựa chọn tốt nhất của tôi - drAlberT
nó có thể không phải là một ý tưởng tốt để sử dụng lambda trong trường hợp này bởi vì lambda thực sự được gọi là mỗi khi từ điển được xây dựng. - htmlfarmer
Đáng buồn thay đây là những người gần nhất sẽ nhận được. Các phương pháp sử dụng .get() (giống như các câu trả lời cao nhất hiện tại) sẽ cần phải háo hức đánh giá tất cả các khả năng trước khi gửi đi, và do đó không chỉ (không chỉ rất nhưng) cực kỳ kém hiệu quả và cũng không thể có tác dụng phụ; câu trả lời này xoay quanh vấn đề đó, nhưng tiết lộ nhiều hơn. Tôi sẽ chỉ sử dụng nếu / elif / else, và ngay cả những người mất chỉ cần lâu để viết là 'trường hợp'. - ninjagecko
điều này sẽ không đánh giá tất cả các hàm / lambdas mọi lúc trong mọi trường hợp, ngay cả khi nó chỉ trả về một trong các kết quả? - slf
@slf Không, khi luồng điều khiển đến được đoạn mã đó, nó sẽ xây dựng 3 hàm (thông qua việc sử dụng 3 lambdas) và sau đó xây dựng một từ điển với 3 hàm đó làm giá trị, nhưng chúng vẫn chưa được khai báo (đánh giá hơi mơ hồ trong bối cảnh đó) lúc đầu. Sau đó, từ điển được lập chỉ mục qua [value], sẽ chỉ trả lại một trong 3 hàm (giả định value là một trong 3 phím). Chức năng này chưa được gọi vào thời điểm đó. Sau đó (x) gọi hàm vừa trả về với x làm đối số (và kết quả là result). 2 chức năng khác sẽ không được gọi. - blubberdiblub


Ngoài các phương thức từ điển (mà tôi thực sự thích, BTW), bạn cũng có thể sử dụng if-elif-else để có được chức năng chuyển đổi / trường hợp / mặc định:

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

Điều này tất nhiên là không giống hệt nhau để chuyển đổi / trường hợp - bạn không thể có mùa thu dễ dàng như rời khỏi giờ nghỉ; tuyên bố, nhưng bạn có thể có một bài kiểm tra phức tạp hơn. Định dạng của nó là đẹp hơn một loạt các ifs lồng nhau, mặc dù chức năng đó là những gì nó là gần gũi hơn với.


248
2017-09-13 01:10



Tôi thực sự thích điều này, nó sử dụng một ngôn ngữ standart xây dựng và không ném một KeyError nếu không tìm thấy trường hợp phù hợp - martyglaubitz
Tôi đã nghĩ về từ điển / get nhưng cách tiêu chuẩn đơn giản là dễ đọc hơn. - Martin Thoma
Không chính xác hoạt động như một tuyên bố chuyển đổi nhưng rất gần. Theo ý kiến ​​gần nhất của tôi - Arijoon
Đây là giải pháp sạch nhất. Nó phổ biến nhất là mỗi switch có một break và việc sử dụng phổ biến nhất cho sự sụt giảm mà tôi thấy là khớp với nhiều phần tử, như trong if x in 'bc':. - bmacnaughton
Tuy nhiên, "Mặc dù không sử dụng elif" là một chút khó hiểu. Điều gì về điều này: quên đi "rơi qua" và chỉ chấp nhận nó là hai if/elif/else'S? - Alois Mahdal


Công thức Python yêu thích của tôi cho chuyển đổi / trường hợp là:

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

Ngắn và đơn giản cho các tình huống đơn giản.

So sánh với 11 dòng mã C:

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

Bạn thậm chí có thể gán nhiều biến bằng cách sử dụng bộ dữ liệu:

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

122
2018-06-17 02:25



Tôi thấy đây là một câu trả lời mạnh mẽ hơn so với chấp nhận. - cerd
@some user: C yêu cầu giá trị trả về phải là cùng loại cho tất cả các trường hợp. Python thì không. Tôi muốn làm nổi bật tính linh hoạt của Python này trong trường hợp ai đó có tình huống bảo đảm việc sử dụng đó. - ChaimG
@some user: Cá nhân, tôi tìm thấy {} .get (,) có thể đọc được. Để có thêm khả năng đọc cho người mới bắt đầu Python, bạn có thể muốn sử dụng default = -1; result = choices.get(key, default). - ChaimG
so sánh với 1 dòng c ++ result=key=='a'?1:key==b?2:-1 - Jasen
@ Jason có thể cho rằng bạn có thể làm điều đó trong một dòng Python cũng như: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). nhưng là một lớp lót có thể đọc được? - ChaimG


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

Sử dụng:

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

Kiểm tra:

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

86
2017-07-07 06:09



Đây không phải là mối đe dọa an toàn. Nếu nhiều công tắc được nhấn cùng một lúc, tất cả các công tắc sẽ lấy giá trị của công tắc cuối cùng. - francescortiz
Trong khi @francescortiz có thể có nghĩa là thread an toàn, nó cũng không đe dọa an toàn. Nó đe dọa các giá trị của các biến! - Zizouz212
Vấn đề an toàn chủ đề có thể được làm việc xung quanh bằng cách sử dụng -bộ nhớ cục bộ. Hoặc có thể tránh được hoàn toàn bằng cách trả về một cá thể và sử dụng cá thể đó để so sánh trường hợp. - blubberdiblub
@blubberdiblub Nhưng sau đó không phải là nó chỉ hiệu quả hơn để sử dụng một tiêu chuẩn if tuyên bố? - wizzwizz4
Điều này cũng không an toàn nếu được sử dụng trong nhiều chức năng. Trong ví dụ được đưa ra, nếu case(2) khối được gọi là một hàm khác sử dụng công tắc (), sau đó khi thực hiện case(2, 3, 5, 7) vv để tìm trường hợp tiếp theo để thực thi, nó sẽ sử dụng giá trị chuyển đổi được thiết lập bởi hàm khác không phải là giá trị được thiết lập bởi câu lệnh chuyển đổi hiện tại. - user9876


Có một mẫu mà tôi đã học được từ mã Python xoắn.

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'

Bạn có thể sử dụng nó bất cứ lúc nào bạn cần gửi một mã thông báo và thực thi đoạn mã mở rộng. Trong một máy nhà nước bạn sẽ có state_ phương pháp và gửi đi self.state. Chuyển đổi này có thể được mở rộng rõ ràng bằng cách kế thừa từ lớp cơ sở và xác định do_ phương pháp. Thường thì bạn thậm chí sẽ không có do_ các phương thức trong lớp cơ sở.

Chỉnh sửa: chính xác được sử dụng như thế nào

Trong trường hợp SMTP bạn sẽ nhận được HELO từ dây. Mã có liên quan (từ twisted/mail/smtp.py, được sửa đổi cho trường hợp của chúng tôi) trông như thế này

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

Bạn sẽ nhận được ' HELO foo.bar.com ' (hoặc bạn có thể nhận được 'QUIT' hoặc là 'RCPT TO: foo'). Điều này được mã hóa thành parts như ['HELO', 'foo.bar.com']. Tên tra cứu phương thức thực tế được lấy từ parts[0].

(Phương pháp ban đầu cũng được gọi là state_COMMAND, bởi vì nó sử dụng cùng một mẫu để triển khai máy trạng thái, tức là getattr(self, 'state_' + self.mode))


41
2017-09-13 01:26



Tôi không thấy lợi ích từ mô hình này qua việc chỉ gọi các phương thức trực tiếp: SMTP (). Do_HELO ('foo.bar.com') OK, có thể có mã phổ biến trong lookupMethod, nhưng vì nó cũng có thể bị ghi đè bởi các lớp con tôi không thấy những gì bạn đạt được từ indirection. - Mr Shark
Bạn sẽ không biết phương thức để gọi trước, đó là để nói 'HELO' xuất phát từ một biến. tôi đã thêm ví dụ sử dụng vào bài đăng gốc
Tôi có thể đề xuất đơn giản: eval ('SMTP (). Do_' + command) ('foo.bar.com') - jforberg
Ngoài ra, tại sao khởi tạo một đối tượng SMTP mới cho mỗi cuộc gọi phương thức? Đó là những chức năng toàn cầu dành cho. - jforberg
eval? nghiêm túc? và thay vì instantiating một phương thức cho mỗi cuộc gọi, chúng ta có thể nhanh chóng khởi tạo một lần và sử dụng nó trong tất cả các cuộc gọi miễn là nó không có trạng thái bên trong. - Mahesh


Cái yêu thích của tôi thực sự rất tuyệt công thức. Bạn sẽ thực sự thích nó. Đó là một trong những gần nhất tôi đã nhìn thấy thực tế chuyển đổi trường hợp báo cáo, đặc biệt là trong các tính năng.

Đây là một ví dụ:

# 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!"

38
2017-07-07 06:12



Tôi sẽ thay thế for case in switch() với with switch() as case, có ý nghĩa hơn, vì nó cần s chỉ chạy một lần. - Ski
@Skirmantas: Lưu ý rằng with không cho phép break tuy nhiên, do đó, tùy chọn bỏ qua được lấy đi. - Jonas Wielicki
Xin lỗi vì đã không nỗ lực nhiều hơn để tự mình xác định điều này: câu trả lời tương tự ở trên không phải là chủ đề an toàn. Co phải đây la? - David Winiecki
@DavidWiniecki Các thành phần mã bị thiếu từ bên trên (và có thể là bản quyền do activestate) xuất hiện là luồng an toàn. - Jasen
một phiên bản khác của cái này sẽ giống như if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"? - mpag