Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Есть ли в Python оператор switch case?

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


Ответ

Для начала, ничего особенно плохого в использовании конструкции 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]
Этот словарь после вычисления будет иметь два ключа 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
# Эквивалентная реализация на 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')
Этот способ использует короткую схему вычисления операторов 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')
Будет работать неправильно в случае 0 <= x < 1, т.к. выражение 0 <= x < 1 and 0 равно 0, и из-за этого управление перейдёт следующему аргументу or, вместо того, чтобы вернуть этот ноль в качестве результата выражения. Использование исключений
Если объявить несколько функций:
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 "Другое число"

Комментариев нет:

Отправить комментарий