Заміни для оператора switch в Python?


1718

Я хочу написати функцію в Python, яка повертає різні фіксовані значення на основі значення вхідного індексу.

В інших мовах я б використовував a switchабо caseоператор, але, схоже, у Python не існує switchзаяви. Які рекомендовані рішення Python у цьому сценарії?


77
Пов’язаний PEP, автор самого Гвідо: PEP 3103
chb

28
@chb У цьому PEP Гідо не згадує, що ланцюги if / elif також є класичним джерелом помилок. Це дуже крихка конструкція.
йогобрюс

15
Відсутність у всіх рішеннях тут - виявлення повторюваних значень справ . Як принцип невдалої роботи, це може бути важливішою втратою, ніж продуктивність або особливість проходу.
Боб Штейн

6
switchнасправді більш "універсальний", ніж те, що повертає різні фіксовані значення на основі значення вхідного індексу. Це дозволяє виконувати різні фрагменти коду. Насправді навіть не потрібно повертати значення. Цікаво, чи деякі відповіді тут є гарною заміною для загального switchтвердження, або лише для випадку повернення значень без можливості виконання загальних фрагментів коду.
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Тендітна конструкція, так само, як цикл у той час, як цикл - це тендітна конструкція, якщо ви спробуєте використовувати його, щоб робити те, що для ... в ... робить. Ви збираєтесь називати програмістів слабкими для використання циклів? Хоча петлі - це все, що їм потрібно. Але для циклів демонструйте чіткий намір, збережіть безглузді котлопластини і дайте можливість створити потужні абстракції.
itsbruce

Відповіді:


1486

Ви можете використовувати словник:

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

100
Що станеться, якщо х не знайдено?
Нік

46
@nick: ви можете скористатися рішенням за замовчуванням
Елі Бендерський,

385
Я рекомендую ставити дикт поза функцією, якщо продуктивність є проблемою, тому вона не перебудовує дикт на кожному виклику функції
Клавдіу

56
@EliBendersky, Використання getметоду, ймовірно, було б більш нормальним, ніж використання collections.defaultdictв цьому випадку.
Майк Грехем

27
@ Nick, Виключення викинуто - зробіть }.get(x, default)замість цього, якщо має бути за замовчуванням. (Примітка. Це набагато приємніше, ніж те, що трапиться, якщо вимкнути за замовчуванням оператор перемикання!)
Майк Грем

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?
Джон Мей

13
@JM: Ну, очевидно, що пошукові словники не підтримують проривів. Ви можете зробити подвійний пошук словника. Тобто "a" & "b" вказують на answer1 і "c" і "d" - на відповідь2, які містяться у другому словнику.
Нік

3
краще передати значення за замовчуванням
HaTiMSuM

Існують проблеми з таким підходом. Перший раз, коли ви телефонуєте f, ви збираєтеся створювати дикт знову, якщо у вас є більш складне значення, ви можете отримати винятки, наприклад. якщо x - кортеж, і ми хочемо зробити щось подібне x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Це підніме IndexError
Ідан Хаїм Шалом

2
@Idan: Питання полягало у повторенні перемикача. Я впевнений, що міг би зламати і цей код, якщо б спробував ввести непарні значення. Так, це буде відтворено, але це просто виправити.
Нік

394

Мені завжди подобалося робити це так

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

Звідси


16
чудовий метод у поєднанні з get () для обробки дефолту теж мій найкращий вибір
drAlberT

27
можливо, це не гарна ідея використовувати лямбда в цьому випадку, оскільки лямбда насправді називається кожен раз, коли словник створений.
Ашер

13
На жаль, це найближчі люди. Методи, які використовують .get()(як і поточні найвищі відповіді), повинні ретельно оцінювати всі можливості перед відправленням, тому не тільки є (не лише дуже, але й) надзвичайно неефективними, а також не можуть мати побічні ефекти; ця відповідь оточує це питання, але є більш багатослівною. Я просто використовую if / elif / else, і навіть для тих, хто пише так само багато часу, як "case".
ninjagecko

13
Хіба це не оцінюватиме всі функції / лямбдати кожен раз у всіх випадках, навіть якщо це повертає лише один із результатів?
slf

23
@slf Ні, коли потік управління досягне цього фрагмента коду, він побудує 3 функції (за допомогою 3 лямбда), а потім створить словник із цими 3-ма функціями як значення, але вони залишаються невимогливими ( оцінювання трохи неоднозначне в той контекст) спочатку. Тоді словник індексується через [value], який поверне лише одну з 3 функцій (припустимо, що valueце одна з 3 клавіш). Функція ще не була викликана. Потім (x)викликає щойно повернуту функцію з xаргументом (і результат переходить до result). Інші 2 функції не будуть викликатися.
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спосіб, але стандартний спосіб просто читабельніший.
Мартін Тома

2
@someuser, але те, що вони можуть "перекриватись", є особливістю. Ви просто переконайтесь, що порядок є пріоритетним, у якому мають відбуватися відповідність. Що стосується повторного х: просто зробіть x = the.other.thingраніше. Як правило, у вас буде одиничний if, множина elif та one one інше, як це легше зрозуміти.
Метью Шинкель

7
Приємно, що "Прохід через не використання elif", хоч трохи заплутано. А що з цього: забути про "провалитися" і просто прийняти це як два if/elif/else?
Алоїза Магдал

7
Також варто згадати, коли використовуєте такі речі x in 'bc', майте на увазі, що "" in "bc"це True.
Ломар АШАР

185

Мій улюблений рецепт Python для перемикача / корпусу:

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

Короткий і простий для простих сценаріїв.

Порівняйте 11+ рядків коду С:

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

Ви навіть можете призначити кілька змінних, використовуючи кортежі:

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

16
Я вважаю це більш надійною відповіддю, ніж прийняте.
серд

3
@ деякий користувач: C вимагає, щоб значення повернення було однакового типу для всіх випадків. Пітон ні. Я хотів підкреслити цю гнучкість Python на випадок, якщо у когось виникла ситуація, яка гарантувала таке використання.
ChaimG

3
@ деякий користувач: Особисто я вважаю {} .get (,) читабельним. Для додаткової читабельності для початківців Python ви можете скористатися default = -1; result = choices.get(key, default).
ChaimG

4
порівняти з 1 рядком с ++result=key=='a'?1:key==b?2:-1
Ясен

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
@Skirmantas: Зауважте, що withце не дозволяє break, тому варіант переходу знімається.
Йонас Шефер

5
Вибачте за те, що не докладаєте більше зусиль для того, щоб визначити це сам: подібна відповідь, наведена вище, не є безпечною. Це?
David Winiecki

1
@DavidWiniecki Компоненти коду, відсутні у вищезазначеному (можливо, авторські права від Activestate), схоже, є безпечними для потоків.
Ясен

чи буде інша версія цього щось подібне 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
Використання контекстних менеджерів - це гарне творче рішення. Я рекомендую додати трохи пояснень і, можливо, посилання на якусь інформацію про менеджерів контексту, щоб надати цій публікації якийсь, ну, контекст;)
Буде

2
Мені дуже не подобається, якщо ланцюги / elif є дуже креативними, але це і найкреативніше, і найпрактичніше з усіх рішень, які я бачив, використовуючи існуючий синтаксис Python.
їїбруса

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'). Це є ознакою partsяк ['HELO', 'foo.bar.com']. Взято власне ім'я пошуку методу parts[0].

(Оригінальний метод також називається state_COMMAND, оскільки він використовує ту саму схему для реалізації державної машини, тобто getattr(self, 'state_' + self.mode))


4
Я не бачу вигоди від цього шаблону в простому виклику методів безпосередньо: SMTP (). Do_HELO ('foo.bar.com') ОК, у lookupMethod може бути загальний код, але оскільки це також може бути замінено підкласу Я не бачу, що ви отримуєте від непрямості.
Містер Шарк

1
Ви не знаєте, який метод зателефонувати заздалегідь, тобто "HELO" походить від змінної. Я додав приклад використання до початкової публікації

Я можу запропонувати просто: eval ('SMTP (). Do_' + команда) ('foo.bar.com')
jforberg

8
eval? серйозно? і замість того, щоб створювати один метод за виклик, ми можемо досить добре інстанціювати один раз і використовувати його у всіх дзвінках за умови, що у нього немає внутрішнього стану.
Махеш

1
ІМО справжнім ключем тут є диспетчеризація за допомогою getattr для визначення функції для запуску. Якщо методи були в модулі, ви можете зробити getattr (localals (), func_name), щоб отримати його. Частина "do_" хороша для безпеки / помилок, тому можна викликати лише функції з префіксом. SMTP сам викликає lookupMethod. В ідеалі зовні нічого про це не знає. Це не має сенсу робити SMTP (). LookupMethod (ім'я) (дані). Оскільки команда та дані в одному рядку, а SMTP аналізує їх, це має більше сенсу. Нарешті, SMTP, ймовірно, має інший загальний стан, що виправдовує його як клас.
ShawnFumo

27

Скажімо, ви не хочете просто повертати значення, а хочете використовувати методи, які щось змінюють на об'єкті. Використовуючи тут підхід, було б:

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

Тут відбувається те, що python оцінює всі методи у словнику. Тож навіть якщо ваше значення "a", об'єкт збільшується і зменшуватися на x.

Рішення:

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

result = func(*args)

Таким чином, ви отримуєте список, що містить функцію та її аргументи. Таким чином, повертаються лише покажчик функції та список аргументів, а не оцінюються. 'result' потім оцінює повернений виклик функції.


23

Я просто піду свої два центи сюди. Причина, що у Python не існує оператора case / switch, полягає в тому, що Python дотримується принципу "Є єдиний правильний спосіб зробити щось". Так що очевидно, ви можете придумати різні способи відтворення функцій перемикачів / корпусів, але пітонічним способом досягнення цього є конструкція 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
Так чому Python має петлі та цикли while? Все, що ви можете зробити з циклом for, який ви можете реалізувати за допомогою циклу.
itsbruce

1
Правда. Switch / case занадто часто зловживають початківцями програмістами. Те, що вони дійсно хочуть, - це стратегія .
користувач228395

Схоже, Python бажає, щоб це було Clojure
TWR Cole

1
@TWRCole Я не думаю, що Python робив це першим. Python існує з 1990 року, а Clojure - з 2007 року.
Тейлор

Є лише один правильний спосіб зробити щось. Python 2.7 або Python 3? Лол.
TWR Коул

17

розширюється на ідею "dict as switch". якщо ви хочете використовувати для переключення значення за замовчуванням:

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

14
Я думаю, що зрозуміліше використовувати .get () у диктаті із зазначеним за замовчуванням. Я вважаю за краще залишати винятки для виняткових обставин, і він скорочує три рядки коду та рівень відступу, не будучи незрозумілими.
Кріс Б.

10
Це є винятковим обставиною. Це може бути або не бути рідкісною обставиною залежно від корисного, але це, безумовно, виняток (відкиньтесь 'default') з правила (дістаньте щось із цього дикту). За дизайном програми Python використовують винятки при падінні капелюха. Однак, використання getпотенційно може зробити код трохи приємнішим.
Майк Грехем,

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

Мені подобається ваше рішення. Але що робити, якщо мені просто потрібно передати деякі змінні чи об'єкти?
Тедо Врбанек

Це не спрацює, якщо метод очікує параметрів.
Куласангар

16

Якщо ви шукаєте додатковий вислів, як "перемикач", я створив модуль python, який розширює Python. Це називається ESPY як "вдосконалена структура для Python", і він доступний як для Python 2.x, так і для Python 3.x.

Наприклад, у цьому випадку оператор перемикання може виконуватися за допомогою наступного коду:

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 полягає в тому, щоб дозволити, але не вимагати пропускання.
Соломон Учко

Чи доступний цей модуль?
Соломон Учко

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)

Замість того, щоб бути оператором, версія python - це вираз, який оцінює значення.


Також замість ... параметр ... і p1 (x) як щодо parameterіp1==parameter
Боб Штейн

@ BobStein-VisiBone привіт, ось приклад, який працює в моєму сеансі python : 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, незалежно від того, це виклик функції чи вираз, це добре.
лео

@ BobStein-VisiBone Так, ви праві! Дякую :) Щоб багаторядковий вираз працював, дужки повинні бути розміщені, як у вашій пропозиції, або як у моєму модифікованому прикладі.
лео

Відмінно. Зараз я видалю всі свої коментарі щодо паронів.
Боб Штейн

15

Більшість відповідей тут досить старі, і особливо прийняті, тому, здається, варто оновити.

По-перше, офіційний FAQ Python охоплює це питання і рекомендує elifланцюжок для простих випадків і dictдля великих або більш складних випадків. Він також пропонує набір visit_методів (стиль, який використовується багатьма серверними рамками) для деяких випадків:

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

У FAQ часто згадується PEP 275 , який був написаний, щоб отримати офіційне рішення про додавання одного разу назавжди про додавання заяв про перемикання стилю C. Але цей PEP був фактично відкладений на Python 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 питання випуску справ у стилі С, або навіть трохи більш потужна версія Go, вважалася мертвою; всякий раз, коли хто-небудь підводить це на ідеях python або -dev, вони посилаються на старе рішення.

Однак ідея повного узгодження шаблону стилю ML виникає кожні кілька років, тим більше, що такі мови, як Swift та Rust, прийняли його. Проблема полягає в тому, що важко отримати багато користі від узгодження шаблонів без алгебраїчних типів даних. Хоча Гвідо прихильно ставився до цієї ідеї, ніхто не придумав пропозиції, яка дуже добре вписується в Python. (Ви можете прочитати мій соломник 2014 року для прикладу.) Це може змінитись dataclassу 3.7 та деяких спорадичних пропозиціях щодо більш потужних enumдля обробки типів суми або з різними пропозиціями щодо різних типів місцевих прив’язок (наприклад, PEP 3150 або сукупність пропозицій, що зараз обговорюються на -іді). Але поки що.

Також періодично пропонуються відповідність стилю 6-го стилю Perl, який, в основному, є метеоризмом всього, починаючи від elifрегулярного виведення та перемикання типу одноразового перемикання.


15

Рішення для запуску функцій:

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

де foo1 (), foo2 (), foo3 () і default () - функції


1
Так, наприклад, якщо змінна опція == "case2" ваш результат = foo2 ()
Алехандро

і так далі, і так далі.
Алехандро Кінтанар

Так, я розумію мету. Але я занепокоєний тим, що якщо ви хочете foo2(), всі foo1(), foo3()і, і default()всі функції також запускаються, це означає, що все може зайняти багато часу
Брайан Андервуд

1
опустити () всередині словника. використання get(option)(). проблема вирішена.
timgeb

1
Відмінно використання () є натерти рішення, я зробив суть , щоб перевірити це gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Алехандро Quintanar

13

Я не знайшов простої відповіді, яку шукав у будь-якому місці пошуку Google. Але я все-таки зрозумів це. Це дійсно досить просто. Вирішив опублікувати його, і, можливо, запобіжить кілька менших подряпин на чужій голові. Ключ просто "в" і кортежі. Ось поведінка оператора перемикання при проходженні, включаючи пропуск 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)

Забезпечує:

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.

Де саме тут провалився?
Йонас Шефер

На жаль! Там є падіння, але я вже не беру участь у Stack Overflow. Їх зовсім не подобається. Мені подобаються внески інших, але тільки не Stackoverflow. Якщо ви використовуєте падіння через FUNCTIONALITY, тоді ви хочете CATCH певні умови у всьому випадку виписки за допомогою перемикача (улов усі), поки ви не отримаєте заяву перерви в комутаторі.
Дж.Д. Грем

2
Тут обидві значення "Собака" та "Кішка" ВИХОВНУЮТЬСЯ через функцію САМО, і вони визначаються як "чотири ноги". Це РЕЗУЛЬТАТ, еквівалентний для проходження та різних значень, оброблених оператором SAME case, коли відбувається перерва.
JD Graham

@JDGraham Я думаю, що Йонас мав на увазі ще один аспект провалу, який трапляється, коли програміст час від часу забуває написати breakв кінці коду для case. Але я думаю, що нам не потрібен такий «провал» :)
Михайло Батцер

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"в дікт і використовує йогоx=23

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

не знаходить його в Словнику і використовує за замовчуванням "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

Розглянемо, включивши короткий опис свого коду та те, як він вирішує розміщене питання
Генрі Вуді,

Гаразд, я зараз додав коментар до цього.
Вік'ят Агарвал

8

Мені сподобалась відповідь Марка Біса

Оскільки 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]()?
Боб Штейн

Так, саме. Я маю на увазіresult = {'a': 100, None:5000}; result[None]
guneysus

4
Просто перевірка того, що ніхто не думає, None:поводиться так default:.
Боб Штейн

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

Короткий і простий для читання, має значення за замовчуванням і підтримує вирази в обох умовах та повернених значеннях.

Однак він менш ефективний, ніж рішення зі словником. Наприклад, Python повинен просканувати всі умови, перш ніж повернути значення за замовчуванням.


7

Ви можете використовувати розісланий диктант:

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

Простий, не перевірений; кожна умова оцінюється незалежно: немає пропускання, але всі випадки оцінюються (хоча вираз для включення оцінюється лише один раз), якщо немає заяви про перерву. Наприклад,

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

Я думаю, що найкращий спосіб - це використовувати ідіоми мови python, щоб тримати код перевіреним . Як було показано в попередніх відповідях, я використовую словники, щоб скористатися структурами і мовою python і тримати код "case" ізольованим у різних методах. Нижче є клас, але ви можете безпосередньо використовувати модуль, глобалі та функції. У класі є методи, які можна перевірити ізольовано . Залежно від ваших потреб, ви також можете грати зі статичними методами та атрибутами.

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: заява перемикача зручна, я згоден. Однак програміст прагне писати дуже довгі заяви і код, який не можна використовувати повторно. Спосіб, який я описав, є більш чистим для тестування та більш багаторазовим використання, IMHO.
J_Zar

@J_Zar: повторно. Мій запит на приклад: так, я розумію, але я намагаюся вкласти це в контекст більшого фрагмента коду. Чи можете ви показати, як я можу це використати в реальній ситуації?
Енді Кліфтон

1
@AndyClifton: Вибачте, я спізнююсь, але я розмістив приклад.
J_Zar

5

Розгортаючись на відповідь Грега Хьюгілла - Ми можемо капсулювати словник-рішення за допомогою декоратора:

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-декоратором

@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 -модулі. Просто встановіть за допомогою 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 (якщо 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?" тому що це часто цікавіше питання і часто виявить недоліки в дизайні того, що ви будуєте.

Тепер це не означає, що і комутатори ніколи не повинні використовуватися. Державні машини, лексери, аналізатори та автомати використовують їх певною мірою і, як правило, коли ви починаєте з симетричного вводу і переходите до асиметричного виходу, вони можуть бути корисними; вам просто потрібно переконатися, що ви не використовуєте вимикач як молоток, тому що в коді ви бачите купу цвяхів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.