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