Я хочу написать функцию на Python, которая возвращает разные фиксированные значения в зависимости от значения входного индекса.
На других языках я бы использовал инструкцию switch или case, но в Python, похоже, нет инструкции switch. Какие решения Python рекомендуются в этом сценарии?
@chb В этом PEP Гвидо не упоминает, что цепочки if / elif также являются классическим источником ошибок. Это очень хрупкая конструкция.
Во всех решениях отсутствует обнаружение повторяющиеся значения регистра. В качестве принципа отказоустойчивости это может быть более серьезная потеря, чем производительность или функция падения.
switch на самом деле более «универсален», чем что-то, возвращающее различные фиксированные значения на основе значения входного индекса. Это позволяет выполнять разные фрагменты кода. На самом деле даже не нужно возвращать значение. Интересно, являются ли некоторые из ответов здесь хорошей заменой для общего оператора switch или только для случая возврата значений без возможности выполнения общих фрагментов кода.
@ sancho.s - согласился. Если вы не вернетесь или не прервете выполнение инструкции switchcase, то в остальных случаях будет выполнен код все. Это явно не то же самое, что набор if / elseifs
@ MalikA.Rumi Хрупкая конструкция, точно так же, как цикл while - хрупкая конструкция, если вы попытаетесь использовать его для того, что ... в ... делает. Вы собираетесь называть программистов слабыми в использовании циклов for? В то время как петли - это все, что им действительно нужно. Но циклы for демонстрируют четкое намерение, сохраняют бессмысленный шаблон и дают возможность создавать мощные абстракции.
Таким же образом синтаксис вроде Ruby case ... when ... (или Scala match, Haskell's case, Perl given / when) соответствует общему варианту использования и предлагает мощную абстракцию. если ... элиф ... плохая замена.
@itsbruce: Мне мало о Haskell или Ruby, и вообще ничего о Perl и Scala, поэтому нам придется пока прекратить эти дебаты, но я не могу не чувствовать, что это мнение больше, чем факт. Хотя может быть весело и интересно. Ваше здоровье.
Афоризм Python о том, что «явное лучше, чем неявное», делает провал в Python нестандартным. Не знаю, грустить мне или радоваться: |






Вы можете использовать словарь:
def f(x):
return {
'a': 1,
'b': 2,
}[x]
Что произойдет, если x не будет найден?
@nick: вы можете использовать defaultdict
Я бы рекомендовал поместить dict вне функции, если производительность является проблемой, чтобы он не перестраивал dict при каждом вызове функции.
@EliBendersky, использование метода get, вероятно, было бы более нормальным, чем использование collections.defaultdict в этом случае.
@Nick, выдается исключение - делай }.get(x, default) вместо него, если должен быть дефолт. (Примечание: это намного лучше, чем то, что произойдет, если вы оставите значение по умолчанию выключенным в инструкции switch!)
жаль, что нет поддержки переключателя / корпуса. Я просто использую операторы if, поскольку предпочитаю, чтобы логика была простой для других и для себя, если я пересмотрю свой код долгое время (и забуду этот потрясающий ответ @Greg Hewgilland и @Nick)
Словарь, реализующий переключатель, также можно использовать в качестве значения функции по умолчанию. Таким образом, он так же эффективен, как и Клаудиу, упомянутый в вышеприведенном комментарии, но по-прежнему является локальным для функции. Проблема в том, что кто-то пытается передать аргумент словаря, который не предназначен для использования извне :) Жизнь полна компромиссов.
Я не могу заставить его работать должным образом при использовании оператора print внутри словаря. ideone.com/aL9EUE
@AndersonGreen: Это другая проблема. Этот вопрос задан по поводу возврата «фиксированных значений». Вам придется использовать другое решение, если вы хотите использовать print (потому что print имеет побочные эффекты, и весь словарь оценивается при его создании).
@GregHewgill К счастью, кто-то уже нашел решение: stackoverflow.com/a/11479840/975097
Когда вы создаете для этого большой словарь и хотите провалиться, это пригодится: stackoverflow.com/a/2974151/724752
Вы также можете просто использовать someDict.getitem(x) или, что еще лучше, someDict[x].
Это лучший ответ на языке каждый, всегда используйте хэш (словарь, ассоциативный массив), он более чистый, чем другие варианты
@pepr: в Python 3 с PEP 3102 вы можете избежать риска случайного изменения аргумента по умолчанию, используя аргументы, содержащие только ключевые слова, например def f(x, *, _lookup = {'a': 1, 'b': 2}): return _lookup.get(x, 3). * без имени означает, что аргументы после этой точки могут передаваться только по имени, поэтому вызов f(1, {}) не приведет к неправильному поведению. Пользователь мог сделать f(1, _lookup = {}), но это его вина. Префиксы подчеркивания обычно означают «только для внутреннего использования», и мы все здесь взрослые; если они хотят игнорировать это правило, это их прерогатива.
Конечно, в этом ограниченном случае вам даже не нужна функция уровня Python, вы можете просто привязать методы dict напрямую, например, без случая по умолчанию, f = {'a': 1, 'b': 2}.__getitem__, или со значением по умолчанию, f = collections.defaultdict(lambda: 3, {'a': 1, 'b': 2}).__getitem__ (хотя последний будет хранить автоматически оживленные пары ключ / значение, поэтому здесь имеет смысл использовать функцию-оболочку, чтобы этого избежать).
Можете ли вы включить пример того, как будет использоваться этот оператор псевдопереключения?
Это общее решение не только для Python, но и для языка в целом. Я не думаю, что люди понимают реальные варианты использования коммутаторов.
Как мне это назвать в Python?
Если действия нетривиальны, вы можете сделать значения словаря функциями и вызывать соответствующий (таблица прыжков / переходов); кто-нибудь хочет опубликовать пример?
вы можете использовать совпадение рано или поздно: stackoverflow.com/questions/59098889/…
x10 неэффективно по сравнению с цепочкой if
синтаксис соответствия был принят и скоро будет официально поддерживаться в python 3!
Мне всегда нравилось так делать
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}[value](x)
отличный метод в сочетании с get () для обработки по умолчанию - тоже мой лучший выбор
Он просит фиксированные значения. Зачем создавать функцию для вычисления чего-либо, когда это поиск? Однако интересное решение для других проблем.
Возможно, в этом случае не стоит использовать лямбда, потому что лямбда фактически вызывается каждый раз при построении словаря.
К сожалению, это самое близкое, что может случиться с людьми. Методы, которые используют .get() (например, текущие самые высокие ответы), должны будут тщательно оценить все возможности перед отправкой, и поэтому они не только (не только очень, но) чрезвычайно неэффективны, но и не могут иметь побочных эффектов; этот ответ обходит эту проблему, но более подробен. Я бы просто использовал if / elif / else, и даже их написание занимает столько же времени, сколько и case.
не будет ли это оценивать все функции / лямбды каждый раз во всех случаях, даже если он возвращает только один из результатов?
Что делать, если вам нужно провалить корпус?
Единственная проблема с этим методом заключается в том, что вы пытаетесь сохранить функции, на которые позже хотите ссылаться на свойства.
@slf Нет, когда поток управления достигает этого фрагмента кода, он построит 3 функции (с использованием 3 лямбда-выражений), а затем создаст словарь с этими 3 функциями в качестве значений, но они останутся невызванными (оценивать немного неоднозначен в этот контекст) сначала. Затем словарь индексируется через [value], который возвращает только одну из трех функций (при условии, что value является одним из трех ключей). Функция еще не вызывалась. Затем (x) вызывает только что возвращенную функцию с x в качестве аргумента (и результат переходит к result). Остальные 2 функции не будут вызваны.
@blubberdiblub ах, поэтому он будет каждый раз создавать все функции, но оценивать только одну из них
@slf Да, именно так и происходит. Хотел бы я сказать это так же коротко, как вы;)
Не для фиксированных значений, а для дорогостоящих функций (время, память и т. д.) Это предпочтительнее, поскольку лямбда-выражения не будут оцениваться во время построения словаря. +1 к комментарию @blubberdiblub за разъяснение этого вопроса.
@ninjagecko, как вы думаете, почему использование get заставляет любой словарь оценивать все значения - это не так. get ищет ключ - результат O (1), а затем, если ключ не существует, возвращает значение по умолчанию - это не хуже, чем: if key in dict: return dict [key] else: return default
@ TonySuffolk66: Вы неправильно поняли мою формулировку; нет такой вещи, как dict, переоценивающий свои значения (которые уже вычислены в неленивом питоне). Я только что сказал, что ответы, которые создают словарь и вызывают .get - чтобы напрямую вернуть значение - должны были уже вычислить эти значения. Вы не должны писать {True:f(), False:g()}[x] (O (f + g)) вместо f() if x else g() (O (max (f, g))). Единственное преимущество - OP запрашивает фиксированные значения, но это доказывает, что нельзя заменить оператор switch на {...} / .get sans lambda, если вы заботитесь об асимптотике. Конечно, накладные расходы на вызов функций тоже плохи.
@ninjagecko - я понимаю, о чем вы говорите - просто ваш комментарий предполагал (ну, как я его читал), что использование get вызывает дополнительную проблему - и я был озадачен.
Вам нужно только построить этот словарь однажды: result = { <dict of lambdas> }, а затем вызвать его с помощью result[val](x). Тогда это становится очень эффективным решением.
Можно ли использовать лямбды с разными параметрами? Т.е. случай 'a' мог иметь lambda x: 2*x, случай 'b' иметь lambda x, y: x + y, случай 'c' иметь lambda y: 2*y ...
Если вы действительно просто возвращаете заранее определенное фиксированное значение, вы можете создать словарь со всеми возможными входными индексами в качестве ключей вместе с их соответствующими значениями. Кроме того, вам может не понадобиться функция для этого, если только вы каким-то образом не вычисляете возвращаемое значение.
О, и если вам хочется сделать что-то вроде переключателя, см. здесь.
В дополнение к словарным методам (которые мне очень нравятся, кстати) вы также можете использовать 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, но вы можете пройти более сложный тест. Его форматирование лучше, чем у серии вложенных if, хотя функционально он ближе к нему.
Я бы предпочел это, он использует стандартную языковую конструкцию и не генерирует KeyError, если соответствующий регистр не найден
Я думал о словарном способе / get, но стандартный способ просто более читабельный.
Не совсем работает как оператор switch, но очень близко. На мой взгляд самое близкое
Это самое чистое решение. Чаще всего у каждого switch есть break, и наиболее распространенное использование провала, которое я вижу, - это сопоставление нескольких элементов, как в if x in 'bc':.
Но переменная x повторяется несколько раз. Если вам нужно заменить x, это намного проще с помощью оператора switch / case. Кроме того, если / elif / else дает вам слишком много свободы (как показано в примере выше, при смешивании == и in, условия могут перекрываться, и это становится трудно читать.
@someuser, но то, что они могут "перекрываться", - это особенность. Вы просто убедитесь, что порядок соответствует приоритету, в котором должны происходить совпадения. Что касается повторного x: просто сделайте x = the.other.thing раньше. Как правило, у вас будет один if, несколько elif и один else, поскольку это легче понять.
Кроме того, не обязательно, чтобы к x каждый раз применялся один и тот же тип теста. т.е. == vs in vs not in или что-то еще.
@Matthew: Проще говоря, если вам нужна такая гибкость, вы используете if/elif/else. Но большинство из тех, кто посещает эту тему, этого не делают. Нам нужен более структурный код. Любой язык, поддерживающий switch, уже поддерживает if/elif/else.
Приятно, однако, «провал из-за неиспользования elif» немного сбивает с толку. А как насчет этого: забыть о «провале» и принять это как два if/elif/else?
Также стоит упомянуть, что при использовании таких вещей, как x in 'bc', имейте в виду, что "" in "bc" - это True.
Это действительно ответ. Конечно, вы можете создать словарную конструкцию, но if/elif является наиболее понятным.
Это, вероятно, самый хрупкий способ разработки этой конструкции, особенно с использованием провалов таким образом, который не является интуитивно понятным и дублирует код.
@ user-63873687 Да, я не писал сквозной код (который, я согласен, очень хрупкий). Интересно, есть ли способ убрать мое имя из ответа, потому что мне действительно не нравится этот шаблон: я сам сейчас неправильно его прочитал.
Кажется более традиционным, чем все остальное. Словари добавляют гибкости выбора и повторного использования, но для небольших случаев не имеют смысла.
Также стоит упомянуть, что вы можете написать if x = 'a': action = 'foo'; result = fn(action) в одной строке без необходимости использования новой строки и отступа. Это может сделать блоки if else более краткими и более похожими на оператор case.
Есть шаблон, который я узнал из кода 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))
Я не вижу преимуществ этого шаблона по сравнению с прямым вызовом методов: SMTP (). Do_HELO ('foo.bar.com') Хорошо, в lookupMethod может быть общий код, но поскольку он также может быть перезаписан подкласс Я не вижу, что вы получите от косвенного обращения.
Вы не знаете, какой метод вызвать заранее, то есть «HELO» исходит от переменной. я добавил пример использования в исходный пост
Могу я предложить просто: eval ('SMTP (). Do_' + command) ('foo.bar.com')
Кроме того, зачем создавать новый объект SMTP для каждого вызова метода? Вот для чего нужны глобальные функции.
eval? серьезно? и вместо того, чтобы создавать экземпляр одного метода для каждого вызова, мы вполне можем создать экземпляр один раз и использовать его во всех вызовах, если он не имеет внутреннего состояния.
ИМО, настоящий ключ здесь - это отправка с использованием getattr для указания функции для запуска. Если бы методы были в модуле, вы могли бы получить их с помощью getattr (locals (), func_name). Часть 'do_' хороша для безопасности / ошибок, поэтому могут быть вызваны только функции с префиксом. Сам SMTP вызывает lookupMethod. В идеале внешний мир ни о чем из этого не знает. На самом деле нет смысла делать SMTP (). LookupMethod (name) (data). Поскольку команда и данные находятся в одной строке, и SMTP анализирует их, это имеет больше смысла. Наконец, SMTP, вероятно, имеет другое общее состояние, которое оправдывает его принадлежность к классу.
Этот метод тревожит. На самом деле, просто используйте dict. Нет нужды в этой навороченной чепухе с getattr.
расширяя идею «диктовать как переключатель». если вы хотите использовать для своего переключателя значение по умолчанию:
def f(x):
try:
return {
'a': 1,
'b': 2,
}[x]
except KeyError:
return 'default'
Я думаю, что проще использовать .get () в dict со значением по умолчанию. Я предпочитаю оставлять исключения для исключительных обстоятельств, и это сокращает три строки кода и уровень отступа, не делая его непонятным.
Это является исключительное обстоятельство. Это может быть или не быть обстоятельством редкий в зависимости от полезности, но это определенно исключение (возврат к 'default') из правила (получите что-то из этого dict). По задумке программы Python используют исключения сразу же. При этом использование get потенциально может немного улучшить код.
Если вам нужны значения по умолчанию, вы можете использовать метод словаря get(key[, default]):
def f(x):
return {
'a': 1,
'b': 2
}.get(x, 9) # 9 is default if x not found
Что, если «a» и «b» соответствуют 1, а «c» и «d» соответствуют 2?
@JM: Очевидно, поиск по словарю не поддерживает провалы. Вы можете выполнить двойной поиск по словарю. Т.е. 'a' и 'b' указывают на answer1, а 'c' и 'd' указывают на answer2, которые содержатся во втором словаре.
лучше передать значение по умолчанию
У этого подхода есть проблемы: сначала каждый раз, когда вы вызываете f, вы собираетесь снова создавать dict, во-вторых, если у вас более сложное значение, вы можете получить исключения ex. если x - кортеж, и мы хотим сделать что-то вроде этого x = ('a') def f (x): return {'a': x [0], 'b': x [1]} .get ( x [0], 9) Это вызовет IndexError
@Idan: Вопрос был в репликации коммутатора. Я уверен, что смогу сломать и этот код, если попробую ввести нечетные значения. Да, воссоздадут, но исправить это несложно.
Решения, которые я использую:
Комбинация из 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.
Допустим, вы не хотите просто возвращать значение, а хотите использовать методы, которые что-то меняют в объекте. Использование изложенного здесь подхода будет:
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' затем оценивает возвращенный вызов функции.
Если вы ищете extra-statement, например, «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")
так что переводите это на 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 Причина для while True: и breaks состоит в том, чтобы позволить, но не требовать провал.
Этот модуль еще доступен?
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.
Это небезопасно. Если несколько переключателей задействованы одновременно, все переключатели принимают значение последнего переключателя.
Хотя @francescortiz, вероятно, означает потокобезопасность, это также небезопасно. Это угрожает значениям переменных!
Проблема безопасности потоков, вероятно, может быть решена с помощью локальное хранилище потока. Или этого можно было бы вообще избежать, вернув экземпляр и используя этот экземпляр для сравнения случаев.
@blubberdiblub Но разве не более эффективно использовать стандартный оператор if?
Переключатель @ wizzwizz4 может возвращать функцию: case=switch( foo ), если он более эффективен, чем указанный выше (или вы имеете в виду меньше ввода)
@Jasen Думаю, я имел в виду набор текста, потому что мы уже знаем, что if будет быстрее.
Кажется, что здесь не происходит нормального провального поведения операторов switch (если только предыдущие случаи не указаны явно, как в примере), что может вводить в заблуждение.
Это также небезопасно при использовании в нескольких функциях. В приведенном примере, если блок case(2) вызвал другую функцию, которая использует switch (), то при выполнении case(2, 3, 5, 7) и т. д. Для поиска следующего случая для выполнения он будет использовать значение switch, установленное другой функцией, а не то, которое установлено текущей оператор переключения.
- 1, это не потокобезопасно и не может быть вызвано рекурсивно. Я серьезно надеюсь, что вы, ребята, этого не сделаете.
Использование глобальной переменной модуля для отслеживания локального состояния - ужасная идея. Что происходит, когда логика switch case вызывает функцию, которая также использует switch? switch.value будет перезаписан, что приведет к нарушению обработки последующих случаев! Ниже представлена гораздо лучшая реализация этого подхода., и люди должны проголосовать против этого ответа и проголосовать за него.
Fallthrough можно было бы более чисто реализовать, используя в вашем классе переменную Fallthrough, которая устанавливается при вызове switch () и может установить для следующего вызова значение true. Например: "select.fallthrough = select.fallthrough или любой ((arg == switch.value для arg в args))" "return select.fallthrough" Затем при тестировании варианта (2, 3, 5, 7) вы можете пропустить «2», и он все равно сработает.
Этот метод тоже неэффективен. Есть два вложенных цикла, и словари лучше, чем цикл for.
Мой любимый - действительно хороший рецепт приготовления. Это самый близкий из тех, что я видел, к реальным операторам switch case, особенно в функциях.
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!"
В некоторых комментариях указывалось, что решение диспетчера контекста, использующее with foo as case, а не for case in foo, может быть более чистым, и для больших операторов switch может быть приятным штрихом линейное, а не квадратичное поведение. Частью значения этого ответа с циклом for является возможность иметь разрывы и провалы, и если мы хотим немного поиграть с нашим выбором ключевых слов, мы можем получить это также в диспетчере контекста:
class Switch:
def __init__(self, value):
self.value = value
self._entered = False
self._broken = False
self._prev = None
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
return False # Allows a traceback to occur
def __call__(self, *values):
if self._broken:
return False
if not self._entered:
if values and self.value not in values:
return False
self._entered, self._prev = True, values
return True
if self._prev is None:
self._prev = values
return True
if self._prev != values:
self._broken = True
return False
if self._prev == values:
self._prev = None
return False
@property
def default(self):
return self()
Вот пример:
# Prints 'bar' then 'baz'.
with Switch(2) as case:
while case(0):
print('foo')
while case(1, 2, 3):
print('bar')
while case(4, 5):
print('baz')
break
while case.default:
print('default')
break
Я бы заменил for case in switch() на with switch() as case, это имеет больше смысла, поскольку он должен запускаться только один раз.
@Skirmantas: Обратите внимание, что with не поддерживает break, поэтому возможность пропадания отсутствует.
Приносим извинения за то, что не приложили больше усилий, чтобы определить это сам: аналогичный ответ выше не является потокобезопасным. Это?
@DavidWiniecki Компоненты кода, отсутствующие в приведенном выше (и, возможно, авторское право activestate), кажутся потокобезопасными.
была бы другая версия этого типа if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"?
Если у вас сложный блок 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()
Мне нравится ваше решение. Но что, если мне просто нужно передать некоторые переменные или объекты?
Это не сработает, если метод ожидает параметры.
Это метод, который официально рекомендуется в FAQ по Python.
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
Короткий и удобный для чтения, имеет значение по умолчанию и поддерживает выражения как в условиях, так и в возвращаемых значениях.
Однако он менее эффективен, чем решение со словарем. Например, Python должен просмотреть все условия, прежде чем вернуть значение по умолчанию.
Простое сопоставление некоторого ключа с некоторым кодом на самом деле не является проблемой, как показало большинство людей, используя dict. Настоящая уловка состоит в том, чтобы подражать всему, что происходит «насквозь». Не думаю, что когда-либо писал case-выражение, в котором я использовал бы эту «особенность». Вот вам шанс.
def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)
case([
(False, lambda:print(5)),
(True, lambda:print(4))
])
Я действительно представлял это как единое утверждение. Надеюсь, вы простите глупое форматирование.
reduce(
initializer=False,
function=(lambda b, f:
( b | f[0]
, { False: (lambda:None)
, True : f[1]
}[b | f[0]]()
)[0]
),
iterable=[
(False, lambda:print(5)),
(True, lambda:print(4))
]
)
Надеюсь, это правильный питон. Это должно дать вам пропасть. конечно, логические проверки могут быть выражениями, и если вы хотите, чтобы они вычислялись лениво, вы можете заключить их все в лямбду. Мне было бы несложно заставить его принять после выполнения некоторых пунктов в списке. Просто создайте кортеж (bool, bool, function), где второй bool указывает, следует ли прерывать или пропускать.
Drop-through неструктурирован. Вам это не нужно, это затруднит поддержку вашего кода, сложнее избежать ошибок, и он запрещен многими моими стандартами кодирования. Это пережиток C. C не стал на 100% структурированным, он остался в drop-through, goto, continue, break. Большинство его потомков скопировали его.
также используйте Список для хранения дел и вызовите соответствующую функцию, выбрав-
cases = ['zero()','one()','two()','three()']
def zero():
print "method for 0 called..."
def one():
print "method for 1 called..."
def two():
print "method for 2 called..."
def three():
print "method for 3 called..."
i = int(raw_input("Enter choice between 0-3 "))
if (i<=len(cases)):
exec(cases[i])
else:
print "wrong choice"
также объяснено в винтовой стол
зачем использовать exec, если вы можете хранить объекты функций в списке, а не в списке строк?
Определение:
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 () объединяет словари перед поиском значения. Он по-прежнему уродливее, чем мне бы хотелось, но его основная эффективность заключается в использовании хешированного поиска по выражению, а не цикла по всем ключам.
Я не нашел простого ответа, который искал нигде в поиске Google. Но я все равно разобрался. Это действительно очень просто. Решил выложить, а может помешать еще несколько царапин на чужой голове. Ключ просто «in» и кортежи. Вот поведение оператора switch при провале, включая случайный провал.
l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']
for x in l:
if x in ('Dog', 'Cat'):
x += " has four legs"
elif x in ('Bat', 'Bird', 'Dragonfly'):
x += " has wings."
elif x in ('Snake',):
x += " has a forked tongue."
else:
x += " is a big mystery by default."
print(x)
print()
for x in range(10):
if x in (0, 1):
x = "Values 0 and 1 caught here."
elif x in (2,):
x = "Value 2 caught here."
elif x in (3, 7, 8):
x = "Values 3, 7, 8 caught here."
elif x in (4, 6):
x = "Values 4 and 6 caught here"
else:
x = "Values 5 and 9 caught in default."
print(x)
Обеспечивает:
Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.
Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
Где именно здесь провал?
Ой! Там есть провалы, но я больше не участвую в Stack Overflow. ИХ вообще не нравятся. Мне нравится вклад других, но только не Stackoverflow. Если вы используете провал для ФУНКЦИОНАЛЬНОСТИ, тогда вы хотите уловить определенные условия во всех в одном операторе case в переключателе (catch all), пока вы не дойдете до оператора break в переключателе.
Здесь оба значения «Собака» и «Кошка» ПАДАЮТ НА ПРОСМОТР и обрабатываются ОДНОЙ функциональностью, то есть они определены как имеющие «четыре ноги». Это АБСТРАКТНЫЙ эквивалент провала и разные значения, обрабатываемые ТО ЖЕ оператором case, когда происходит разрыв.
@JDGraham Я думаю, что Джонас имел в виду еще один аспект провала, который случается, когда программист иногда забывает написать break в конце кода для case. Но думаю, нам не нужен "провал" такой :)
Понравился Ответ Марка Биса
Поскольку переменная 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
Разве случай None не эмулирует просто result[None]()?
Да, точно. Я имею ввиду result = {'a': 100, None:5000}; result[None]
Просто проверяю, что никто не думает, что None: ведет себя как 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 - это не выражение, а выражение, которое вычисляет значение.
Также вместо ... параметра ... и p1 (x) как насчет parameter и p1==parameter
@ BobStein-VisiBone привет, вот пример, который выполняется в моем сеансе Python: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Когда я позже позвонил f(2), я получил 'c'; f(1), 'b'; и f(0), 'a'. Что касается p1 (x), он обозначает предикат; пока он возвращает True или False, независимо от того, является ли это вызовом функции или выражением, все в порядке.
@ BobStein-VisiBone Да, вы правы! Спасибо :) Чтобы многострочное выражение работало, следует заключить круглые скобки, как в вашем предложении, или как в моем модифицированном примере.
Отлично. Теперь я удалить все мои комментарии про паренсы.
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
Использование контекстных менеджеров - хорошее творческое решение. Я бы порекомендовал добавить небольшое объяснение и, возможно, ссылку на некоторую информацию о менеджерах контекста, чтобы дать этому посту некоторый, ну, контекст;)
Мне не очень нравятся цепочки if / elif, но это и самое креативное, и самое практичное из всех решений, которые я видел с использованием существующего синтаксиса Python.
Это действительно здорово. Одно из предлагаемых улучшений - добавить (общедоступное) свойство value к классу Switch, чтобы вы могли ссылаться на case.value в операторе.
Этот ответ предоставляет наиболее функциональные возможности переключателя, хотя и довольно прост. Проблема с использованием dict заключается в том, что вы можете только получать данные и не можете запускать функции / методы.
Python> = 3.10 (предварительная версия)
Ух ты, Python теперь получает настоящий синтаксис match / case!
PEP 634, 'структурное сопоставление с образцом'; a.k.a. switch / case, был одобрен и добавлен в Python 3.10.
match something:
case 0 | 1 | 2:
print("Small number")
case [] | [_]:
print("A short sequence")
case str() | bytes():
print("Something string-like")
case _:
print("Something else")
Python <= 3.9
Мой любимый рецепт переключателя / корпуса на 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'))
Я считаю, что это более надежный ответ, чем принятый.
Обратите внимание, что 2 сегмента кода не совпадают. Версия python возвращает значение по умолчанию, если совпадений нет, но версия C возвращает -1. Когда вы заменяете строку на choices.get(key, -1), ее становится труднее читать, потому что людям приходится делать паузу, чтобы подумать, как работает get () и что означает -1.
@some user: C требует, чтобы возвращаемое значение было одного и того же типа для всех случаев. Python этого не делает. Я хотел выделить эту гибкость Python на тот случай, если у кого-то возникнет ситуация, которая потребует такого использования.
@some user: Лично я нахожу {} .get (,) читабельным. Для дополнительной читабельности для начинающих Python вы можете использовать default = -1; result = choices.get(key, default).
сравните с 1 строкой C++ result=key=='a'?1:key==b?2:-1
@Jasen можно утверждать, что вы можете сделать это и в одной строке Python: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). но читается ли один лайнер?
@ChaimG tbh C one liner довольно читаем в этих простых случаях
Дополнительным преимуществом этого подхода является более четкое разделение кода и данных. Это упрощает передачу данных в качестве параметра (упрощает задачу написания тестов) или, например, перемещение их в файл конфигурации.
Такова проблема сравнения языков по тому, сколько строк нужно, чтобы выразить идею. Обычно вы можете выполнить что-то в Python с помощью одной строки, и вы можете всегда выразить что-то с помощью одной строки в C.
обратите внимание, что вам не хватает функций, которые считаются присущими операторам switch, например, провал
этот подход каждый раз выделяет память для словаря?
Этот вопрос касается исключительно Python, а не сравнения Python и C (или C++). У каждого языка есть свои преимущества и недостатки. Практически любой оператор может не выполняться во время выполнения, если не выполняется надлежащая проверка типов, в то время как языки с статической типизацией, поскольку C++ обнаруживает их во время компиляции. С другой стороны, Python включает в себя батареи, с огромным количеством модулей. Можете ли вы назвать любой из языков лучшим?
# 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
Думаю, лучший способ - это используйте идиомы языка Python, чтобы ваш код оставался тестируемым. Как показано в предыдущих ответах, я использую словари для воспользоваться преимуществами структур и языка Python и сохраняю "case" код изолированным в разных методах. Ниже приведен класс, но вы можете напрямую использовать модуль, глобальные объекты и функции. В классе есть методы, которые можно протестировать с изоляцией. В зависимости от ваших потребностей вы также можете играть со статическими методами и атрибутами.
class ChoiceManager:
def __init__(self):
self.__choice_table = \
{
"CHOICE1" : self.my_func1,
"CHOICE2" : self.my_func2,
}
def my_func1(self, data):
pass
def my_func2(self, data):
pass
def process(self, case, data):
return self.__choice_table[case](data)
ChoiceManager().process("CHOICE1", my_data)
Можно Воспользуйтесь этим методом, используя также классы в качестве ключей из «__choice_table». Таким образом вы можете избежать это пример злоупотребления и сохранить все в чистоте и тестируемости.
Предположим, вам нужно обработать много сообщений или пакетов из сети или вашего MQ. Каждый пакет имеет свою собственную структуру и свой управляющий код (в общем виде). С помощью приведенного выше кода можно сделать что-то вроде этого:
class PacketManager:
def __init__(self):
self.__choice_table = \
{
ControlMessage : self.my_func1,
DiagnosticMessage : self.my_func2,
}
def my_func1(self, data):
# process the control message here
pass
def my_func2(self, data):
# process the diagnostic message here
pass
def process(self, pkt):
return self.__choice_table[pkt.__class__](pkt)
pkt = GetMyPacketFromNet()
PacketManager().process(pkt)
# isolated test or isolated usage example
def test_control_packet():
p = ControlMessage()
PacketManager().my_func1(p)
Итак, сложность не распространяется в потоке кода, но отображается в структуре кода.
Действительно некрасиво ... Корпус переключателя такой чистый при чтении. Не могу понять, почему это не реализовано на Python.
@AndyClifton: Мне очень жаль ... пример? Подумайте о каждом случае, когда вам нужно иметь код ветвления с несколькими решениями, и вы можете применить этот метод.
@ jmcollin92: оператор switch удобен, я согласен. Однако программист имеет тенденцию писать очень длинные операторы и код, который нельзя использовать повторно. Способ, который я описал, чище для тестирования и более пригоден для повторного использования, ИМХО.
@J_Zar: re. Мой запрос на пример: да, я понимаю, но я изо всех сил пытаюсь поместить это в контекст более крупного фрагмента кода. Не могли бы вы показать, как я могу использовать это в реальной ситуации?
@AndyClifton: Извините, я опоздал, но я опубликовал примерный случай.
Если вы не беспокоитесь о потере выделения синтаксиса в наборах 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')
""")
Расширение Ответ Грега Хьюджилла - мы можем инкапсулировать словарь-решение с помощью декоратора:
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
Прочитав ответ, я был весьма сбит с толку, но это все прояснило:
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";
}
}
Проверьте Источник, чтобы узнать больше о сопоставлении словаря функциям.
Оператор switch - это просто синтаксический сахар для if / elif / else. То, что делает любой управляющий оператор, - это делегирование задания на основе определенного условия, которое выполняется - путь решения. Для включения этого в модуль и возможности вызывать задание на основе его уникального идентификатора можно использовать наследование и тот факт, что любой метод в python является виртуальным, чтобы обеспечить реализацию конкретного задания производного класса, как конкретный обработчик "case"
#!/usr/bin/python
import sys
class Case(object):
"""
Base class wich specifies the interface for "case" handler.
The all required arbitrary arguments inside "execute" method will be
provided through the derived class
specific constructor
@note in python, all class methods are virtual
"""
def __init__(self, id):
self.id = id
def pair(self):
"""
Pairs the given id of the "case" with
the instance on which "execute" will be called
"""
return (self.id, self)
def execute(self):#base class virtual method that needs to be overrided
pass
class Case1(Case):
def __init__(self, id, msg):
self.id = id
self.msg = msg
def execute(self):#override the base class method
print("<Case1> id = {}, message: \"{}\"".format(str(self.id), self.msg))
class Case2(Case):
def __init__(self, id, n):
self.id = id
self.n = n
def execute(self):#override the base class method
print("<Case2> id = {}, n = {}.".format(str(self.id), str(self.n)))
print("\n".join(map(str, range(self.n))))
class Switch(object):
"""
The class wich delegates the jobs
based on the given job id
"""
def __init__(self, cases):
self.cases = cases#dictionary: time complexitiy for access operation is 1
def resolve(self, id):
try:
cases[id].execute()
except KeyError as e:
print("Given id: {} is wrong!".format(str(id)))
if __name__ == '__main__':
# Cases
cases=dict([Case1(0, "switch").pair(), Case2(1, 5).pair()])
switch = Switch(cases)
# id will be dynamically specified
switch.resolve(0)
switch.resolve(1)
switch.resolve(2)
Я просто брошу сюда свои два цента. Причина, по которой в Python нет оператора case / switch, заключается в том, что Python следует принципу «Есть только один правильный способ что-то сделать». Итак, очевидно, что вы могли бы придумать различные способы воссоздания функциональности переключателя / корпуса, но способ 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, в том числе «Есть только один правильный способ что-то сделать».
Итак, почему в Python есть циклы for и while? Все, что вы можете сделать с помощью цикла for, вы можете реализовать с помощью цикла while.
Истинный. Начинающие программисты слишком часто злоупотребляют переключателем / регистром. Что им действительно нужно, так это шаблон стратегии.
Похоже, Python хочет, чтобы это был Clojure
@ T.W.R.Cole: Я так не думаю, Python сделал это первым. Python существует с 1990 года, а Clojure - с 2007 года.
Есть только один правильный способ что-то сделать. Python 2.7 или Python 3? Ржунимагу.
Очевидно, что принцип дизайна - это не декларация совершенства.
Цитата из PEP 20, Zen of Python: «Должен быть один - и желательно только один - очевидный способ сделать это».
Нет поддержки Fallthru
Я сделал реализацию Switch Case, которая не совсем использует ifs извне (она все еще использует if в классе).
class SwitchCase(object):
def __init__(self):
self._cases = dict()
def add_case(self,value, fn):
self._cases[value] = fn
def add_default_case(self,fn):
self._cases['default'] = fn
def switch_case(self,value):
if value in self._cases.keys():
return self._cases[value](value)
else:
return self._cases['default'](0)
Используйте это так: -
from switch_case import SwitchCase
switcher = SwitchCase()
switcher.add_case(1, lambda x:x+1)
switcher.add_case(2, lambda x:x+3)
switcher.add_default_case(lambda _:[1,2,3,4,5])
print switcher.switch_case(1) #2
print switcher.switch_case(2) #5
print switcher.switch_case(123) #[1, 2, 3, 4, 5]
Получите повышение производительности, если замените if value in keys блоком try-except.
Следующее работает для моей ситуации, когда мне нужен простой switch-case для вызова кучи методов, а не просто для печати некоторого текста. После игры с лямбдой и глобальными переменными он показался мне самым простым вариантом. Может быть, это и кому-то поможет:
def start():
print("Start")
def stop():
print("Stop")
def print_help():
print("Help")
def choose_action(arg):
return {
"start": start,
"stop": stop,
"help": print_help,
}.get(arg, print_help)
argument = sys.argv[1].strip()
choose_action(argument)() # calling a method from the given string
сделайте закрытие ваших функций действий - т.е. определите их в вашей функции choose_action - и вы также можете получить доступ к любым аргументам и т.д. :-), т.е. это станет моим решением ...
И еще вариант:
def fnc_MonthSwitch(int_Month): #### Define a function take in the month variable
str_Return = "Not Found" #### Set Default Value
if int_Month==1: str_Return = "Jan"
if int_Month==2: str_Return = "Feb"
if int_Month==3: str_Return = "Mar"
return str_Return; #### Return the month found
print ("Month Test 3: " + fnc_MonthSwitch( 3) )
print ("Month Test 14: " + fnc_MonthSwitch(14) )
Хотя ответов уже достаточно, я хочу указать более простое и мощное решение:
class Switch:
def __init__(self, switches):
self.switches = switches
self.between = len(switches[0]) == 3
def __call__(self, x):
for line in self.switches:
if self.between:
if line[0] <= x < line[1]:
return line[2]
else:
if line[0] == x:
return line[1]
return None
if __name__ == '__main__':
between_table = [
(1, 4, 'between 1 and 4'),
(4, 8, 'between 4 and 8')
]
switch_between = Switch(between_table)
print('Switch Between:')
for i in range(0, 10):
if switch_between(i):
print('{} is {}'.format(i, switch_between(i)))
else:
print('No match for {}'.format(i))
equals_table = [
(1, 'One'),
(2, 'Two'),
(4, 'Four'),
(5, 'Five'),
(7, 'Seven'),
(8, 'Eight')
]
print('Switch Equals:')
switch_equals = Switch(equals_table)
for i in range(0, 10):
if switch_equals(i):
print('{} is {}'.format(i, switch_equals(i)))
else:
print('No match for {}'.format(i))
Выход:
Switch Between:
No match for 0
1 is between 1 and 4
2 is between 1 and 4
3 is between 1 and 4
4 is between 4 and 8
5 is between 4 and 8
6 is between 4 and 8
7 is between 4 and 8
No match for 8
No match for 9
Switch Equals:
No match for 0
1 is One
2 is Two
No match for 3
4 is Four
5 is Five
No match for 6
7 is Seven
8 is Eight
No match for 9
Простой, не проверенный; каждое условие оценивается независимо: нет провала, но оцениваются все случаи (хотя выражение для включения оценивается только один раз), если нет оператора 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.(Dammit! Why can't I have trailing whitespace in inline code blocks?), если expression оценивается как 1, Was 2., если expression оценивается как 2, или Was something., если expression оценивается как что-то еще.
Ну, провал работает, но только для перехода в do_default.
Решение для запуска функций:
result = {
'case1': foo1,
'case2': foo2,
'case3': foo3,
}.get(option)(parameters_optional)
где foo1 (), foo2 () и foo3 () - функции
Пример 1 (с параметрами):
option = number['type']
result = {
'number': value_of_int, # result = value_of_int(number['value'])
'text': value_of_text, # result = value_of_text(number['value'])
'binary': value_of_bin, # result = value_of_bin(number['value'])
}.get(option)(value['value'])
Пример 2 (без параметров):
option = number['type']
result = {
'number': func_for_number, # result = func_for_number()
'text': func_for_text, # result = func_for_text()
'binary': func_for_bin, # result = func_for_bin()
}.get(option)()
Да, например, если ваша переменная option == "case2" ваш результат = foo2 ()
и так далее.
Да, я понимаю цель. Но меня беспокоит то, что если вам нужен только foo2(), все функции foo1(), foo3() и default() также будут работать, а это означает, что это может занять много времени.
Вы совершенно правы, эта простая версия запускает все функции при создании каталога, вы можете решить эту проблему, передав 'option' в качестве параметра функции в foo (), вот рабочий пример: gist.github.com/anonymous/a5ed3953e28440b4368c338b1e19d294
опустите () внутри словаря. используйте get(option)(). задача решена.
Отлично, использование () - отличное решение, я постарался проверить его gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
Я нашел следующий ответ от документы python наиболее полезным:
Вы можете сделать это достаточно легко с помощью последовательности if... elif... elif... else. Было несколько предложений по синтаксису оператора switch, но пока нет единого мнения о том, следует ли и как проводить тесты диапазона. См. PEP 275 для получения полной информации и текущего статуса.
Для случаев, когда вам нужно выбрать из очень большого количества возможностей, вы можете создать словарь, отображающий значения case для вызываемых функций. Например:
def function_1(...):
...
functions = {'a': function_1,
'b': function_2,
'c': self.method_1, ...}
func = functions[value]
func()
Вызов методов для объектов можно упростить еще больше, используя встроенную функцию getattr () для получения методов с определенным именем:
def visit_a(self, ...):
...
...
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
Рекомендуется использовать префикс для имен методов, например visit_ в этом примере. Без такого префикса, если значения поступают из ненадежного источника, злоумышленник сможет вызвать любой метод для вашего объекта.
PEP 275, похоже, отклонен.
Большинство ответов здесь довольно старые, особенно общепринятые, поэтому кажется, что их стоит обновить.
Во-первых, официальный 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 был отклонен, проблема операторов case в стиле C или даже немного более мощной версии в Go была признана мертвой; всякий раз, когда кто-то упоминает об этом на python-ideas или -dev, они ссылаются на старое решение.
Однако идея полного сопоставления с образцом в стиле ML возникает каждые несколько лет, особенно после того, как такие языки, как Swift и Rust, приняли ее. Проблема в том, что трудно извлечь пользу из сопоставления с образцом без алгебраических типов данных. Хотя Гвидо с пониманием относился к этой идее, никто не придумал предложения, которое бы хорошо вписывалось в Python. (Вы можете прочитать мой соломенный человек 2014 года в качестве примера.) Это может измениться с dataclass в 3.7 и некоторыми спорадическими предложениями по более мощному enum для обработки типов сумм, или с различными предложениями для различных видов локальных привязок операторов (например, PEP 3150 или set предложений, обсуждаемых в настоящее время по -идеи). Но пока этого не произошло.
Также время от времени появляются предложения о сопоставлении в стиле Perl 6, которое, по сути, представляет собой мешанину всего, от elif до регулярного выражения и переключения типов с одной отправкой.
В качестве незначительного изменения Ответ Марка Бика для необычных случаев, таких как этот дубликат, где у пользователя есть куча вызовов функций для задержки с аргументами для упаковки (и не стоит создавать кучу функций вне очереди) вместо этого :
d = {
"a1": lambda: a(1),
"a2": lambda: a(2),
"b": lambda: b("foo"),
"c": lambda: c(),
"z": lambda: z("bar", 25),
}
return d[string]()
… вы можете сделать это:
d = {
"a1": (a, 1),
"a2": (a, 2),
"b": (b, "foo"),
"c": (c,)
"z": (z, "bar", 25),
}
func, *args = d[string]
return func(*args)
Это конечно короче, но вопрос о том, является ли это более читаемый…
Я думаю, что было бы более читабельно (хотя и не короче) переключиться с lambda на partial для этого конкретного использования:
d = {
"a1": partial(a, 1),
"a2": partial(a, 2),
"b": partial(b, "foo"),
"c": c,
"z": partial(z, "bar", 25),
}
return d[string]()
… Который имеет то преимущество, что прекрасно работает и с аргументами ключевого слова:
d = {
"a1": partial(a, 1),
"a2": partial(a, 2),
"b": partial(b, "foo"),
"c": c,
"k": partial(k, key=int),
"z": partial(z, "bar", 25),
}
return d[string]()
Проблема с этим ответом в том, что все аргументы по-прежнему оцениваются немедленно. Даже если сначала это сработает для пользователя, если однажды ему придется добавить корпус foo(some_expensive_function(3)), это станет проблемой. Либо все ломается, либо им приходится отдельно проверять этот случай перед словарем, что не только выглядит странно, но и может ввести в заблуждение читателя, который с первого взгляда подумает, что единственные варианты, которые следует учитывать, находятся в словаре.
@AlexHall Когда у вас есть кусок произвольного кода, который нужно отложить, а не просто вызов функции, который нужно отложить, вам действительно нужно обернуть его функцией. И для чего-то менее тривиального, чем игрушечный пример, который вы только что привели, это, вероятно, даже не лямбда, а вне очереди с именем def, которое вам нужно. Но я не думаю, что это недостаток языка или что-то в этом роде, потому что такой код действительно не является - и не должен быть - распространенным. Если у вас есть более реалистичный пример, вероятно, есть лучший способ его реорганизовать.
Вы правы, мой пример можно решить, просто написав lambda: foo(some_expensive_function(3)) для этого случая. Я добавил мой собственный ответ по этим строкам.
Подобно этот ответ от abarnert, вот решение специально для случая использования вызова одной функции для каждого «случая» в коммутаторе, избегая при этом lambda или partial для сверхкороткости, но при этом имея возможность обрабатывать аргументы ключевого слова:
class switch(object):
NO_DEFAULT = object()
def __init__(self, value, default=NO_DEFAULT):
self._value = value
self._result = default
def __call__(self, option, func, *args, **kwargs):
if self._value == option:
self._result = func(*args, **kwargs)
return self
def pick(self):
if self._result is switch.NO_DEFAULT:
raise ValueError(self._value)
return self._result
Пример использования:
def add(a, b):
return a + b
def double(x):
return 2 * x
def foo(**kwargs):
return kwargs
result = (
switch(3)
(1, add, 7, 9)
(2, double, 5)
(3, foo, bar=0, spam=8)
(4, lambda: double(1 / 0)) # if evaluating arguments is not safe
).pick()
print(result)
Обратите внимание, что это вызовы цепочки, то есть switch(3)(...)(...)(...). Не ставьте запятые между ними. Также важно поместить все это в одно выражение, поэтому я использовал дополнительные круглые скобки вокруг основного вызова для неявного продолжения строки.
Приведенный выше пример вызовет ошибку, если вы включите значение, которое не обрабатывается, например switch(5)(1, ...)(2, ...)(3, ...). Вместо этого вы можете указать значение по умолчанию, например switch(5, default=-1)... возвращает -1.
Решение, которое я обычно использую, в котором также используются словари:
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)()
Это имеет то преимущество, что он не пытается каждый раз оценивать функции, и вам просто нужно убедиться, что внешняя функция получает всю информацию, которая нужна внутренним функциям.
Легко вспомнить:
while True:
try:
x = int(input("Enter a numerical input: "))
except:
print("Invalid input - please enter a Integer!");
if x==1:
print("good");
elif x==2:
print("bad");
elif x==3:
break
else:
print ("terrible");
До сих пор было много ответов, в которых говорилось: «У нас нет переключателя в 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 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
Рассмотрите возможность включения краткого описания вашего кода и того, как он решает поставленный вопрос.
Хорошо, я сейчас добавил к этому комментарий.
вы можете использовать отправленный 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
Я иногда использую это, но это не так ясно, как if / elif / elif / else
Связанный PEP, созданный самим Гвидо: PEP 3103