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

1718

Я хочу написать функцию в Python, которая возвращает различные фиксированные значения в зависимости от значения входного индекса.

В других языках я бы использовал оператор 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 Хрупкая конструкция, так же как цикл while является хрупкой конструкцией, если вы пытаетесь использовать ее, чтобы делать то, что для ... в ... делает. Собираетесь ли вы назвать программистов слабыми для использования в циклах? Пока петли - это все, что им действительно нужно. Но петли показывают четкое намерение, сохраняют бессмысленный шаблон и дают возможность создавать мощные абстракции.
Брюс

Ответы:

1486

Вы можете использовать словарь:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
Грег Хьюгилл
источник
100
Что произойдет, если х не найден?
Ник
46
@nick: вы можете использовать defaultdict
Эли Бендерски,
385
Я бы порекомендовал ставить dict вне функции, если производительность является проблемой, поэтому она не перестраивает dict при каждом вызове функции
Claudiu
56
@EliBendersky, Использование getметода, вероятно, будет более нормальным, чем использование collections.defaultdictв этом случае.
Майк Грэм
27
@Nick, выдается исключение - делайте }.get(x, default)вместо этого, если должно быть значение по умолчанию. (Примечание: это намного лучше, чем то, что происходит, если вы оставите значение по умолчанию выключенным оператором switch!)
Майк Грэм
1374

Если вы хотите использовать значения по умолчанию, вы можете использовать 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» указывают на answer2, которые содержатся во втором словаре.
Ник
3
лучше передать значение по умолчанию
HaTiMSuM
С этим подходом есть проблемы: во-первых, каждый раз, когда вы вызываете f, вы снова создаете dict, во-вторых, если у вас более сложное значение, вы можете получить исключения ex. если 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 () для обработки
значений
27
Возможно, в этом случае не очень хорошая идея использовать лямбду, потому что лямбда на самом деле вызывается каждый раз, когда создается словарь.
Ашер
13
К сожалению, это самые близкие люди собираются получить. Методы, которые используют .get()(как текущие самые высокие ответы), должны будут с нетерпением оценить все возможности перед отправкой, и поэтому не только (не только очень, но) чрезвычайно неэффективны и также не могут иметь побочных эффектов; этот ответ обходит эту проблему, но более многословен. Я бы просто использовал if / elif / else, и даже те, которые пишутся так же, как «case», занимают столько же времени.
ninjagecko
13
Разве это не будет оценивать все функции / лямбды каждый раз во всех случаях, даже если он возвращает только один из результатов?
SLF
23
@slf Нет, когда поток управления достигает этого куска кода, он создаст 3 функции (используя 3 лямбда-выражения), а затем создаст словарь с этими тремя функциями в качестве значений, но они останутся невостребованными ( оценка немного неоднозначна в этот контекст) на первых порах. Затем словарь индексируется через [value], который будет возвращать только одну из 3 функций (при условии, что valueэто один из 3 ключей). Эта функция еще не была вызвана. Затем (x)вызывает только что возвращенную функцию с xаргументом as (и результат отправляется в result). Другие 2 функции не будут вызваны.
blubberdiblub
354

В дополнение к методам словаря (которые мне действительно нравятся, кстати, вы также можете использовать 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 и еще один, поскольку это легче понять.
Мэтью Шинкель
7
Хорошо, "провал, не используя elif", немного сбивает с толку. Как насчет этого: забыть о «провалиться» и просто принять это как два if/elif/else?
Алоис Махдал
7
Также стоит упомянуть, что при использовании таких вещей, как x in 'bc', имейте в виду, что "" in "bc"это так True.
Lohmar ASHAR
185

Мой любимый рецепт Python для switch / case:

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

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

Сравните с 11+ строками кода C:

// 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'))
ChaimG
источник
16
Я считаю, что это более надежный ответ, чем принятый.
КЛРД
3
@ Некоторые пользователи: C требует, чтобы возвращаемое значение было одинаковым для всех случаев. Python нет. Я хотел подчеркнуть эту гибкость Python на тот случай, если у кого-то возникнет ситуация, которая оправдывает такое использование.
ChaimG
3
@ Некоторые пользователи: Лично я нахожу {} .get (,) читабельным. Для дополнительной читаемости для начинающих Python вы можете использовать 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.
adamh
источник
64
Это небезопасно. Если несколько переключателей нажаты одновременно, все переключатели принимают значение последнего переключателя.
francescortiz
48
Хотя @francescortiz скорее всего означает потокобезопасность, это также не безопасно. Это угрожает значениям переменных!
Zizouz212
7
Вероятно, проблему безопасности потока можно обойти, используя локальное хранилище потока . Или этого можно избежать, просто возвращая экземпляр и используя его для сравнения случаев.
blubberdiblub
6
@blubberdiblub Но разве не эффективнее использовать стандартное ifутверждение?
wizzwizz4
9
Это также не безопасно, если используется в нескольких функциях. В приведенном примере, если case(2)блок вызвал другую функцию, которая использует switch (), то при выполнении команды case(2, 3, 5, 7)etc для поиска следующего выполняемого случая он будет использовать значение switch, установленное другой функцией, а не значение, установленное текущим оператором switch. ,
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, имеет больше смысла, так как это нужно с , чтобы запустить только один раз.
Ski
4
@Skirmantas: Обратите внимание, что withэто не breakучитывает, поэтому опцию отмены.
Йонас Шефер
5
Извиняюсь за то, что не приложил больше усилий, чтобы определить это сам: подобный ответ выше не является потокобезопасным. Это?
Дэвид Винецкий
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
Мне не очень нравятся цепочки if / 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? шутки в сторону? и вместо того, чтобы создавать один метод для каждого вызова, мы можем очень легко создать один экземпляр и использовать его во всех вызовах, если у него нет внутреннего состояния.
Mahesh
1
IMO, настоящий ключ здесь - диспетчеризация с использованием getattr для указания функции для запуска. Если бы методы были в модуле, вы могли бы получить getattr (locals (), 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)

Таким образом, вы получите список, содержащий функцию и ее аргументы. Таким образом, только указатель функции и список аргументов будут возвращены, а не оценены. Затем «результат» оценивает возвращенный вызов функции.

GeeF
источник
23

Я просто собираюсь бросить свои два цента здесь. Причина, по которой в Python нет оператора case / switch, заключается в том, что Python следует принципу «есть только один правильный способ сделать что-то». Очевидно, что вы могли бы придумать различные способы воссоздания функциональности switch / case, но 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, в том числе «Есть только один правильный способ сделать что-то»

user2233949
источник
6
Так почему же в Python есть циклы for и while? Все, что вы можете сделать с помощью цикла for, вы можете реализовать с помощью цикла while.
Брюс
1
Правда. Переключатель / регистр слишком часто используются начинающими программистами. То, что они действительно хотят, это шаблон стратегии .
user228395
Похоже, Python хотел бы, чтобы это был Clojure
TWR Коул
1
@TWRCole Я так не думаю, Python делал это первым. Python существует с 1990 года, а Clojure - с 2007 года.
Тейлор,
Есть только один правильный способ сделать что-то. Python 2.7 или Python 3? Лол.
TWR Коул
17

развивая идею «диктуй как переключатель». если вы хотите использовать значение по умолчанию для вашего коммутатора:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
Джереми Кантрелл
источник
14
Я думаю, что яснее использовать .get () в dict с указанным по умолчанию. Я предпочитаю оставлять исключения для исключительных обстоятельств, и это сокращает три строки кода и уровень отступа, не будучи неясным.
Крис Б.
10
Это является исключительным обстоятельством. Это может быть или не быть редким обстоятельством, зависящим от полезного, но это определенно исключение (отступление 'default') из правила (получить что-то из этого диктата). По замыслу, программы на Python используют исключения без промедления. При этом использование getможет потенциально сделать код немного приятнее.
Майк Грэм
16

Если у вас сложный блок case, вы можете рассмотреть возможность использования таблицы поиска словаря функций ...

Если вы еще этого не сделали, неплохо было бы зайти в ваш отладчик и посмотреть, как словарь ищет каждую функцию.

Примечание: не используйте «()» в случае / поиск в словаре , или он будет вызывать каждый из ваших функций , как создается словарь / корпус блока. Запомните это, потому что вы хотите вызывать каждую функцию только один раз, используя поиск в хэш-стиле.

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

Если вы ищете дополнительный оператор как «switch», я создал модуль python, расширяющий Python. Это называется ESPY как «Улучшенная структура для 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 заключается в том, чтобы разрешить, но не требовать провала .
Соломон Уцко
Этот модуль еще доступен?
Соломон Уко
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 привет, вот пример , который работает в моей питона сессии: 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 был отклонен, вопрос о заявлениях в стиле C или даже о более мощной версии в Go считался мертвым; всякий раз, когда кто-нибудь поднимает вопрос о python-ideas или -dev, они обращаются к старому решению.

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

Также иногда появляются предложения по сопоставлению в стиле Perl 6, что, по сути, является путаницей всего, от elifрегулярных выражений до однократного переключения типов.

abarnert
источник
15

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

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

где foo1 (), foo2 (), foo3 () и default () являются функциями

Алехандро Кинтанар
источник
1
Да, например, если ваша переменная option == "case2", ваш результат = foo2 ()
Алехандро Кинтанар
и так и так далее.
Алехандро Кинтанар
Да, я понимаю цель. Но меня беспокоит то, что если вы только хотите foo2(), функции foo1(), foo3()и default()и все также будут работать, а это значит, что все может занять много времени
Брайан Андервуд
1
опустите () внутри словаря. использовать get(option)(). задача решена.
Timgeb
1
Превосходное использование () - это отличное решение, я решил
Алехандро Кинтанар
13

Я не нашел ни одного простого ответа в поиске в Google. Но я все равно понял это. Это действительно довольно просто. Решил опубликовать это, и, возможно, предотвратить несколько меньше царапин на голове другого человека. Ключ просто "в" и кортежи. Вот поведение оператора switch при переходе через 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.
Дж. Д. Грэм
источник
Где именно здесь это падение?
Йонас Шефер
К сожалению! Там есть провал, но я больше не участвую в переполнении стека. Не люблю их вообще. Мне нравится вклад других, но только не Stackoverflow. Если вы используете провал для FUNCTIONALITY, то вы хотите ЗАДЕРЖАТЬ определенные условия во всех в одном операторе case в переключателе (catch all), пока не достигнете оператора break в переключателе.
Дж.Д. Грэм
2
Здесь оба значения «Собака» и «Кошка» ЧЕРЕЗ ЧЕРЕЗ и обрабатываются ОДНОЙ ЖЕ функциональностью, то есть они определены как имеющие «четыре ноги». Это абстракция, эквивалентная абстракции, и различные значения обрабатываются тем же оператором case, где происходит разрыв.
Дж. Д. Грэм,
@JDGraham Я думаю, что Джонас имел в виду еще один аспект, который случается, когда программист иногда забывает написать breakв конце кода для a case. Но я думаю, что нам не нужен такой «прорыв» :)
Михаил Батцер
12

Решения, которые я использую:

Здесь размещена комбинация из двух решений, которые относительно легко читаются и поддерживают значения по умолчанию.

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)

не находит его в dict и использует значение по умолчанию "lambda x: x - 22"с x=44.

thomasf1
источник
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
user5224656
источник
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

guneysus
источник
Разве дело None не подражает просто 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

Вы можете использовать отправленный dict:

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

Простой, не проверенный; Каждое условие оценивается независимо: не существует никакого провала, но оцениваются все случаи (хотя включаемое выражение оценивается только один раз), если только нет оператора 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. (Черт возьми! Почему у меня не может быть конечных пробелов во встроенных блоках кода?), если 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)

Таким образом, сложность не распространяется в потоке кода, а отображается в структуре кода .

J_Zar
источник
Действительно некрасиво ... корпус переключателя так чист при чтении. Не могу понять, почему это не реализовано в Python.
jmcollin92
@AndyClifton: извините ... пример? Подумайте о том, что каждый раз, когда вам нужно иметь несколько ветвящихся кодов решений, вы можете применить этот метод.
J_Zar
@ Jmcollin92: оператор switch удобен, я согласен. Однако программист имеет тенденцию писать очень длинные операторы и код, который нельзя использовать повторно. Способ, который я описал, более чистый для тестирования и более пригодный для повторного использования, ИМХО.
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-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 . Просто установите используя 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)()

Преимущество этого состоит в том, что он не пытается оценивать функции каждый раз, и вам просто нужно убедиться, что внешняя функция получает всю информацию, которая нужна внутренним функциям.

Тони Саффолк 66
источник
5

До сих пор было много ответов, в которых говорилось: «У нас нет переключателя в Python, сделайте это так». Тем не менее, я хотел бы отметить, что сам оператор switch является легко злоупотребляемой конструкцией, которую можно и нужно избегать в большинстве случаев, потому что она способствует ленивому программированию. Дело в точке:

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

Однако такого рода операции можно и нужно выполнять с помощью словаря, поскольку он будет быстрее, менее сложным, менее подверженным ошибкам и более компактным.

И подавляющее большинство «вариантов использования» для операторов switch попадет в один из этих двух случаев; просто очень мало причин использовать его, если вы тщательно продумали свою проблему.

Итак, вместо того, чтобы спрашивать «как я могу переключиться в Python?», Возможно, нам следует спросить: «почему я хочу переключиться в Python?» потому что это часто более интересный вопрос и часто выявляет недостатки в дизайне всего, что вы строите.

Нельзя сказать, что переключатели никогда не должны использоваться. Конечные автоматы, лексеры, парсеры и автоматы все в некоторой степени используют их, и, в общем, когда вы начинаете с симметричного входа и переходите к асимметричному выходу, они могут быть полезны; вам просто нужно убедиться, что вы не используете переключатель в качестве молотка, потому что вы видите кучу гвоздей в своем коде.

Woody1193
источник