Столкнулся с тем, что требуется реализовать множественное условие, которое в других языках я бы реализовал с помощью конструкции switch-case. В Python мне приходится расписывать всё через условия if-elif-else. Это мне кажется довольно неудобным. Есть ли более удобный способ записи подобных условий? Например, у меня есть единицы измерения и в зависимости от выбранной мне нужно вернуть соответствующий множитель: def get_multiplier(unit): if unit == 'mm': return 10**-3 if unit == 'cm': return 10**-2 if unit == 'dm': return 10**-1 if unit == 'm': return 1 if unit == 'km': return 10**3 raise ValueError('Undefined unit: {}'.format(unit))
Ответ UPD (16.02.2022) Обновление добавлено по причине появления конструкции match-case в python 3.10. Ответ, который дополняет в этой ветке и расширенный ответ в связи с изменениями. Ответ ниже продолжает быть актуальным, как в версиях ниже 3.10, так и в версии 3.10 UPD END Для начала, ничего особенно плохого в использовании конструкции if-elif-else нет. При желании можно найти несколько альтернатив.
Использование словарей Довольно распространённый способ организации конструкции switch-case в Python — это использование словаря. Проще показать на примере: unit_to_multiplier = { 'mm': 10**-3, 'cm': 10**-2, 'dm': 10**-1, 'm': 1, 'km': 10**3 } Для того, чтобы получить нужный множитель в этом случае требуется лишь взять значение по ключу: try: mult = unit_to_multiplier['cm'] except KeyError as e: # можно также присвоить значение по умолчанию вместо бросания исключения raise ValueError('Undefined unit: {}'.format(e.args[0])) Если вы твёрдо уверены, что значение всегда будет присутствовать в словаре, можете опустить блок try-except и быть готовым ловить исключение в другом месте. Некоторой вариацией этого подходя будет предварительная проверка значения в условии: if unit in unit_to_multiplier: mult = unit_to_multiplier[unit] else: # обработка отсутствия значения в словаре В Python принято использовать подход, звучащий примерно так: "лучше попробовать и получить ошибку, чем каждый раз спрашивать разрешение", поэтому более предпочтительный подход с использованием исключений. Если хочется использовать значение по умолчанию в случае, если ключ отсутствует, удобно использовать метод get: mult = unit_to_multiplier.get('ultra-meter', 0) Если словарь вам требуется один раз, можно объединить эти выражения в одно: unit_to_multiplier = { 'mm': 10**-3, 'cm': 10**-2, 'dm': 10**-1, 'm': 1, 'km': 10**3 }.get('km', 0)
На этом возможности этого подхода не заканчиваются. Можно использовать условные выражения в качестве ключей словаря: def get_temp_description(temp): return { temp < -20: 'Холодно', -20 <= temp < 0: 'Прохладно', 0 <= temp < 15: 'Зябко', 15 <= temp < 25: 'Тепло', 25 <= temp: 'Жарко' }[True] <br>Этот словарь после вычисления будет иметь два ключа True и False. Нас интересует ключ True. Будьте внимательны, что условия не перекрываются! Подобные словари могут проверять произвольное свойство, например, тип (источник примера): selector = { type(x) == str : "it's a str", type(x) == tuple: "it's a tuple", type(x) == dict : "it's a dict" }[1] # можно использовать число 1 как синоним True При необходимости более сложных действий хранить функцию в качестве значения по каждому ключу: import operator operations = { '+': operator.add, '*': lambda x, y: x * y, # ... } def calc(operation, a, b): return operations[operation](a, b) Будьте внимательны при использовании словаря с функциями — убедитесь, что вы не вызываете эти функции внутри словаря, а передаёте по ключу иначе все функции будут выполняться каждый раз при конструировании словаря.
Другие способы Приведены скорее для ознакомления, чем для реального использования. Использование функций с шаблонными именами Создадим класс, в котором напишем несколько методов вида: def process_first(self): ... def process_second(self): ... ... И один метод-диспетчер: def dispatch(self, value): method_name = 'process_' + str(value) method = getattr(self, method_name) return method() После этого можно использовать метод dispatch для выполнения соответствующей функции, передавая её суффикс, например x.dispatch('first'). Использование специальных классов Если есть желание использовать синтаксис switch-case в максимально похожем стиле, можно написать что-то вроде следующего кода: class switch(object): def __init__(self, value): self.value = value # значение, которое будем искать self.fall = False # для пустых case блоков def __iter__(self): # для использования в цикле for """ Возвращает один раз метод match и завершается """ yield self.match raise StopIteration def match(self, *args): """ Указывает, нужно ли заходить в тестовый вариант """ if self.fall or not args: # пустой список аргументов означает последний блок case # fall означает, что ранее сработало условие и нужно заходить # в каждый case до первого break return True elif self.value in args: self.fall = True return True return False Используется следующим образом: x = int(input()) for case in switch(x): if case(1): pass if case(2): pass if case(3): print('Число от 1 до 3') break if case(4): print('Число 4') if case(): # default print('Другое число') Использование операторов and и or. Довольно небезопасный способ, см. пример: # Условная конструкция Select Case x Case x<0 : y = -1 Case 0<=x<1 : y = 0 Case 1<=x<2 : y = 1 Case 2<=x<3 : y = 2 Case Else : y = 'n/a' End Select <br> # Эквивалентная реализация на Python y = (( x < 0 and 'first segment') or (0 <= x < 1 and 'second segment') or (1 <= x < 2 and 'third segment') or (2 <= x < 3 and 'fourth segment') or 'other segment')<br>Этот способ использует короткую схему вычисления операторов and и or, т.е. то, что логические выражения вычисляются следующим образом: (t вычисляющееся как True, f вычисляется как False): f and x = f t and x = x f or x = x t or x = t Предложенный способ вычислений будет работать только в том случае, если второй аргумент оператора and всегда будет содержать True-выражение, иначе этот блок всегда будет пропускаться. Например: y = (( x < 0 and -1) or (0 <= x < 1 and 0) or (1 <= x < 2 and 1) or (2 <= x < 3 and 2) or 'n/a')<br>Будет работать неправильно в случае 0 <= x < 1, т.к. выражение 0 <= x < 1 and 0 равно 0, и из-за этого управление перейдёт следующему аргументу or, вместо того, чтобы вернуть этот ноль в качестве результата выражения.<br>Использование исключений Если объявить несколько функций: import sys class case_selector(Exception): def __init__(self, value): # один обязательный аргумент Exception.__init__(self, value) def switch(variable): raise case_selector(variable) def case(value): exc_сlass, exс_obj, _ = sys.exc_info() if exc_сlass is case_selector and exс_obj.args[0] == value: return exс_class return None Здесь используется функция sys.exc_info, которая возвращает набор из информации об обрабатываемом исключении: класса, экземпляра и стека. Код с использованием этих конструкций будет выглядеть следующим образом: n = int(input()) try: switch(n) except ( case(1), case(2), case(3) ): print "Число от 1 до 3" except case(4): print "Число 4" except: print "Другое число"