Ответ 1
Вы можете использовать словарь:
def f(x):
return {
'a': 1,
'b': 2,
}[x]
Я хочу написать функцию в Python, которая возвращает разные фиксированные значения на основе значения входного индекса.
В других языках я бы использовал оператор switch
или case
, но у Python не было оператора switch
. Каковы рекомендуемые решения Python в этом сценарии?
Вы можете использовать словарь:
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
Это, конечно, не идентично переключателю/случаю - вы не можете провалиться так же легко, как оставить инструкцию break;, но у вас может быть более сложный тест. Его форматирование лучше, чем серия вложенных ifs, даже если функционально то, к чему оно ближе.
Мой любимый рецепт Python для переключателя/случая:
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.
Вот шаблон, который я узнал из кода 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)
)
Мой любимый рецепт действительно хороший. Вам действительно понравится. Это самый близкий из тех, что я видел в реальных операторах, особенно в функциях.
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!"
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 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)
Итак, вы получаете список, содержащий функцию и ее аргументы. Таким образом, возвращается только указатель функции и список аргументов, а не вычисляются. "результат" затем оценивает вызов возвращенной функции.
Я просто собираюсь бросить здесь свои два цента. Причина в том, что в Python нет оператора case/switch, потому что Python следует принципу "Theres только один правильный способ сделать что-то". Таким образом, очевидно, что вы можете придумать различные способы воссоздания функциональности переключателя/случая, но питонический способ выполнения этого - построить 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, включая "Есть только один правильный способ сделать что-то"
расширяется по идее "dict as switch". если вы хотите использовать значение по умолчанию для вашего коммутатора:
def f(x):
try:
return {
'a': 1,
'b': 2,
}[x]
except KeyError:
return 'default'
Если вы ищете дополнительный оператор, как "switch", я построил модуль python, который расширяет Python. Он назвал ESPY "Enhanced Structure for 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 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()
Я не нашел простой ответ, который я искал в любом месте в поиске Google. Но я все равно понял это. Это очень просто. Решил опубликовать его и, возможно, предотвратить несколько менее царапин на кого-то другого. Ключ - это просто "в" и кортежи. Вот поведение оператора switch с провалом, включая RANDOM 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 представляет собой выражение, которое оценивает значение.
Решения, которые я использую:
Комбинация из двух решений, размещенных здесь, что относительно легко читать и поддерживает значения по умолчанию.
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
.
Большинство ответов здесь довольно старые, и особенно принятые, поэтому, кажется, стоит обновить.
Во-первых, официальные Python FAQ затрагивают это и рекомендуют цепочку 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 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). Но пока это не так.
Есть также иногда предложения для сопоставления в стиле 6 в стиле Perl, что в основном представляет собой путаницу всего от elif
до regex до однонаправленного переключения типов.
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
# 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 как этот смысл. Если результат функции переключателя является вызываемым, он автоматически вызывается.
Мне понравился ответ Mark Bies
Так как переменная 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
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
Короткий и легко читаемый, имеет значение по умолчанию и поддерживает выражения в обоих условиях и возвращает значения.
Однако он менее эффективен, чем решение со словарем. Например, Python должен проверять все условия перед возвратом значения по умолчанию.
Решение для запуска функций:
result = {
'case1': foo1,
'case2': foo2,
'case3': foo3,
'default': default,
}.get(option)()
где foo1(), foo2(), foo3() и default() являются функциями
Я думаю, что лучший способ - использовать идиомы языка 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)
Таким образом, сложность не распространяется в потоке кода, но отображается в структуре кода.
Расширение на Greg Hewgill answer - Мы можем инкапсулировать словарное решение с помощью декоратора:
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 -module. Просто установите с помощью pip:
pip install NeoPySwitch
Простой, не проверенный; Каждое условие оценивается независимо: нет сквозного перехода, но оцениваются все случаи (хотя включаемое выражение вычисляется только один раз), если только нет оператора 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
оценивает что-то еще.
Решение, которое я использую, также использует словари:
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)()
Это имеет то преимущество, что он не пытается каждый раз оценивать функции, и вам просто нужно убедиться, что внешняя функция получает всю информацию, необходимую для внутренних функций.
Вы можете использовать отправленный диктовку:
#!/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
Определение:
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() объединяет словари, прежде чем искать значение. Он по-прежнему уродливее, чем хотелось бы, но он имеет базовую эффективность использования хешированного поиска в выражении, а не цикла через все ключи.