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

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

На других языках я бы использовал switch или же case заявление, но Python, похоже, не имеет switch заявление. Каковы рекомендуемые решения Python в этом сценарии?

48 ответов

Решение

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

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

Если вы хотите значения по умолчанию, вы можете использовать словарь get(key[, default]) метод:

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

Мне всегда нравилось делать это так

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

Отсюда

В дополнение к методам словаря (которые мне действительно нравятся, 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

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

Мой любимый рецепт 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'))
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.

Мой любимый рецепт действительно хороший. Вам действительно понравится. Это самый близкий из тех, что я видел в реальных операторах переключения, особенно в функциях.

Вот пример:

# 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!"
class Switch:
    def __init__(self, value): self._val = value
    def __enter__(self): return self
    def __exit__(self, type, value, traceback): return False # Allows traceback to occur
    def __call__(self, *mconds): return self._val in mconds

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

Есть шаблон, который я узнал из кода 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))

Я сделал это маленькое и чистое решение

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

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

Я просто собираюсь бросить свои два цента здесь. Причина, по которой в 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, в том числе "Есть только один правильный способ сделать что-то"

Допустим, вы не хотите просто возвращать значение, а хотите использовать методы, которые что-то изменяют в объекте. Использование подхода, изложенного здесь, будет:

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)

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

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

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

ПРИМЕЧАНИЕ. Не используйте "()" внутри поиска case/ Dictionary, иначе он вызовет каждую из ваших функций при создании блока словаря / 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()

Если вы ищете дополнительный оператор как "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

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

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

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

Во-первых, официальный 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 или набор предложений, обсуждаемых в настоящее время на -ideas). Но пока что нет.

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

Я обнаружил, что общая структура переключателя:

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 является выражением, которое оценивается как значение.

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

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

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,

Вы можете использовать отправленный 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

Я не нашел ни одного простого ответа в поиске 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.

Прочитав ответ, я был совершенно сбит с толку, но это все прояснило:

def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")

Этот код аналогичен:

function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}

Проверьте источник для получения дополнительной информации о сопоставлении словаря с функциями.

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

Мне понравился ответ Марка Биса

Так как 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

Вдохновлен этим удивительным ответом. Не требует внешнего кода. Не испытано. Падение не работает должным образом.

for case in [expression]:
    if case == 1:
        do_stuff()
        # Fall through

    # Doesn't fall through INTO the later cases
    if case in range(2, 5):
        do_other_stuff()
        break

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

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

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

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

До сих пор было много ответов, в которых говорилось: "У нас нет переключателя в 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?" потому что это часто более интересный вопрос и часто выявляет недостатки в дизайне всего, что вы строите.

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

Решение, которое я склонен использовать, которое также использует словари:

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

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

Определение:

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() объединяет словари перед поиском значения. Это все еще страшнее, чем хотелось бы, но в нем есть базовая эффективность использования хешированного поиска в выражении, а не цикла по всем ключам.

Другие вопросы по тегам