Замены для оператора switch в Python?
Я хочу написать функцию в Python, который возвращает различные фиксированные значения на основе значения Индекса ввода.
на других языках я бы использовал switch
или case
оператор, но Python, похоже, не имеет switch
заявление. Каковы рекомендуемые решения Python в этом сценарии?
30 ответов
Если вы хотите, по умолчанию вы можете использовать словарь 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)
в дополнение к методам словаря (которые мне очень нравятся, кстати), вы также можете использовать 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
Это, конечно, не идентично switch/case - вы не можете иметь провал так же легко, как оставить break; заявление, но вы можете иметь более сложный тест. Его форматирование лучше, чем ряд вложенных ifs, хотя функционально это то, к чему он ближе.
мой любимый рецепт 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.
есть шаблон, который я узнал из скрученного кода 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_
методы в базовом классе.
Edit: как именно это используется
в случае 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
as ['HELO', 'foo.bar.com']
. Фактическое имя поиска метода берется из parts[0]
.
(исходный метод также называется state_COMMAND
, потому что он использует тот же шаблон для реализации государственной машины, т. е. getattr(self, 'state_' + self.mode)
)
мой любимый-очень приятно рецепт. Тебе понравится. Это самый близкий, который я видел к фактическим заявлениям о переключении, особенно в функциях.
вот пример:
# 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
предположим, вы не хотите просто возвращать значение, но хотите использовать методы, которые изменяют что-то на объекте. Используя подход, изложенный здесь, было бы:
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' оценивает возвращаемый вызов функции.
расширение идеи "дикт как переключатель". если вы хотите использовать значение по умолчанию для коммутатора:
def f(x):
try:
return {
'a': 1,
'b': 2,
}[x]
except KeyError:
return 'default'
Если у вас есть сложный блок case, вы можете рассмотреть возможность использования таблицы поиска словаря функций...
Если вы не сделали этого раньше, рекомендуется войти в отладчик и посмотреть, как именно словарь ищет каждую функцию.
Примечание: Do не используйте "() " внутри поиска case/dictionary или он вызовет каждую из ваших функций, когда будет создан блок 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()
Если вы ищете extra-statement, как "switch", я построил модуль python, который расширяет Python. Это называется Варни как "расширенная структура для 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
Я не нашел простой ответ, который я искал в любом месте поиска Google. Но я все равно догадался. Все очень просто. Решил опубликовать его и, возможно, предотвратить несколько меньших царапин на чьей-то голове. Ключ просто " в " и кортежи. Вот поведение оператора switch с fall-through, включая случайное падение.
l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']
for x in l:
if x in ('Dog', 'Cat'):
x += " has four legs"
elif x in ('Bat', 'Bird', 'Dragonfly'):
x += " has wings."
elif x in ('Snake',):
x += " has a forked tongue."
else:
x += " is a big mystery by default."
print(x)
print()
for x in range(10):
if x in (0, 1):
x = "Values 0 and 1 caught here."
elif x in (2,):
x = "Value 2 caught here."
elif x in (3, 7, 8):
x = "Values 3, 7, 8 caught here."
elif x in (4, 6):
x = "Values 4 and 6 caught here"
else:
x = "Values 5 and 9 caught in default."
print(x)
обеспечивает:
Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.
Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
я обнаружил, что общая структура коммутатора:
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 является выражением, которое вычисляет значение.
решения, которые я использую:
комбинация 2 решений, размещенных здесь, которая относительно проста в чтении и поддерживает значения по умолчанию.
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)
здесь
.get('c', lambda x: x - 22)(23)
ищет "lambda x: x - 2"
в dict и использует его с x=23
.get('xxx', lambda x: x - 22)(44)
не находит его в dict и использует значение по умолчанию "lambda x: x - 22"
С x=44
.
# 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
Я сделал (относительно) гибкое и повторно используемое решение для этого. Его можно найти в GitHub как в этом суть. Если результат функции switch вызывается, он вызывается автоматически.
мне понравилось Марк Бии по
С 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'
Edit: я заметил, что я могу использовать None
введите с помощью словарей. Так что это будет подражать switch ; case else
большинство ответов здесь довольно старые, и особенно принятые, поэтому, кажется, стоит обновить.
во-первых, официальная Python FAQ покрывает это, и рекомендует elif
цепи для простых случаев и dict
для больших или более сложных случаях. Он также предлагает набор visit_
методы (стиль, используемый многими серверными фреймворками) для некоторых случаев:
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
в FAQ также упоминается PEP 275, который был написан, чтобы получить официальное раз и навсегда решение о добавлении операторов коммутатора C-style. Но этот 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-style case-заявлениях или даже немного более мощной версии в Go считался мертвым; всякий раз, когда кто-то поднимает его на python-ideas ор-Дев, они ссылаются на старое решение.
тем не менее, идея полного сопоставления шаблонов ML-стиля возникает каждые несколько лет, тем более, что такие языки, как Swift и Rust, приняли его. Проблема в том, что трудно получить много пользы от сопоставления шаблонов без алгебраических типов данных. В то время как Гвидо сочувствовал этой идее, никто не придумал предложение, которое очень хорошо вписывается в Python. (Вы можете читать мой 2014 соломенный для примера.) Это может изменить с dataclass
в 3.7 и отдельные предложения для более мощного enum
для обработки типов сумм или с различными предложениями для различных видов операторов-локальные привязки (например,PEP 3150, или набор предложений, обсуждаемых в настоящее время по-идеям). Но пока этого не произошло.--9-->
есть также иногда предложения для Perl 6-style matching, который в основном является мешаниной всего из elif
к regex к тип-переключению одиночн-отправки.
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
Short и легко читается, имеет значение по умолчанию и поддерживает выражения как в условиях, так и в возвращаемых значениях.
однако, это менее эффективно, чем решение со словарем. Например, Python должен просмотреть все условия, прежде чем возвращать значение по умолчанию.
Я думаю, что лучший способ-это используйте идиомы языка python, чтобы ваш код был проверяемым. Как показано в предыдущих ответах, я использую словари для воспользуйтесь преимуществами структур и языка python и держите код "case" изолированным в разных методах. Ниже есть класс, но вы можете использовать непосредственно модуль, глобалы и функции. Класс имеет методы, которые смогите быть испытано с изоляцией. Зависящ к вашим потребностям, вы можете сыграть с static методы и атрибуты тоже.
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". Таким образом, вы можете избежать злоупотребление isinstance и держите все чистым и проверяемым.
Предположим, вам нужно обработать много сообщений или пакетов из сети или вашего 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)
Так сложность не распространяется в потоке кода, но она отображается в структуре кода.
Я сделал это маленькое и чистое решение
result = {
'case1': foo1,
'case2': foo2,
'case3': foo3,
'default': default,
}.get(option)()
где foo1(), foo2 (), foo3() и 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 () консолидирует словари прежде чем искать значение. Это все еще уродливее, чем мне хотелось бы, но он имеет базовую эффективность использования хэшированного поиска в выражении, а не цикла через все ключи.
Я был довольно смущен после прочтения ответа, но это все прояснило:
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";
}
}
Регистрация источник подробнее о сопоставлении словаря с функциями.
решение, которое я обычно использую, которое также использует словари:
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)()
это имеет то преимущество, что он не пытается оценивать функции каждый раз, и вы просто должны убедиться, что внешняя функция получает всю информацию, необходимую внутренним функциям.
вдохновленный этот удивительный ответ!--3-->. Не требует внешнего кода. Не тестировать. Провал не работает должным образом.
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()
если вы не беспокоитесь о потере подсветки синтаксиса внутри наборов case, вы можете сделать следующее:
exec {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}.get(value, """
print ('None')
""")
здесь value
значение. В C это будет:
switch (value) {
case 1:
printf("one");
break;
case 2:
printf("two");
break;
case 3:
printf("three");
break;
default:
printf("None");
break;
}
мы также можем создать вспомогательную функцию для этого:
def switch(value, cases, default):
exec cases.get(value, default)
поэтому мы можем использовать его так для примера с одним, двумя и тремя:
switch(value, {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}, """
print ('None')
""")
расширения Грег Hewgill это - мы можем инкапсулировать словарь-решение с помощью декоратора:
def case(callable):
"""switch-case decorator"""
class case_class(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def do_call(self):
return callable(*self.args, **self.kwargs)
return case_class
def switch(key, cases, default=None):
"""switch-statement"""
ret = None
try:
ret = case[key].do_call()
except KeyError:
if default:
ret = default.do_call()
finally:
return ret
Это можно затем использовать с @case
-оформителя
@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