Полный гайд по реализации switch-case в питоне
В Python нет привычного оператора Switch как в других языках и после Java, JavaScript или PHP переходя на Python бывает непривычно писать логику без switch-case инструкций. Даже в Bash есть case оператор. Но выход есть и не один! Рассмотрим все варианты реализации логики switch-case на Python.
А еще этот вопрос встречал на собеседованиях. Так что велком под кат, будем разбираться...
Вариант 1: Словари
Самый простой и распространенный вариант - это использовать словари в качестве switch конструкции:
x = 'bar'
switch = {
‘foo’: 123,
‘bar’: 456,
‘buz’: 789,
}[x]
print(switch)
Логика проста: мы обращаемся к нужному нам значению через ключ словаря. Такой же подход можно использовать и в других языках, например в JavaScript:
let x = ‘bar’
let switch = {
‘foo’: 123,
‘bar’: 456,
‘buz’: 789,
}[x] ?? null
console.log(switch)
Заметили разницу? Её почти нет. Обратили внимание что мы использовали Nullish Coalescing оператор? Это на случай если обратимся к несуществующему ключу. А в Python как быть?
Да, в Python есть что-то похожее:
x = ‘wtf’
switch = {
‘foo’: 123,
‘bar’: 456,
‘buz’: 789,
}[x] or Null
Traceback (most recent call last):
File “switch.py”, line 3, in <module>
switch = {
KeyError: ‘wtf’
Но, к сожалению, в таком варианте условие не успеет отработать и будет ошибка. Но у словаря уже есть метод get
который вернет None в случае отсутствия ключа:
x = ‘wtf’
switch = {
‘foo’: 123,
‘bar’: 456,
‘buz’: 789,
}.get(x)
Мы так же можем использовать условия и писать такие конструкции:
def get_size_text(x: int):
return {
x < 10: ‘Small’,
10 <= x < 20: ‘Medium’,
20 <= x: ‘Big’
}[True]
print(get_size_text(13))
Но это еще не все.
Классы
Тут уже на что хватит фантазии, но по сути все варианты сводятся к следующим нескольким идеям.
Идея с цепочками (chaining)
Мы можем написать такой простенький класс:
class Switch:
def init(self, val):
self.val = val
def case(self, val, f):
if self.val == val:
f()
return self
def switch_foo():
print(‘Foo’)
def switch_bar():
print(‘Bar’)
def switch_buz():
print(‘Buz’)
x = ‘bar’
Switch(x)
.case(‘foo’, switch_foo)
.case(‘bar’, switch_bar)
.case(‘buz’, switch_buz)
Громоздко, конечно, но можно делать более сложную логику.
Другой вариант:
class switch(object):
def init(self, val):
self.emp = False
self.val = val
def __iter__(self):
yield self.match
raise StopIteration
def match(self, *args):
if self.emp or not args:
return True
elif self.val in args:
self.emp = True
return True
return False
x = 2
for case in switch(x):
if case(1):
print(‘Foo’)
if case(2):
print(‘Bar’)
break
if case(3):
print(‘Buz’)
# default
if case():
print(‘Default’)
Честно говоря уже too much. Но если хочется чего-то эдакого…
Тернарный оператор
Конечно привычного тернарного оператора в Python так же нет, но есть возможность писать короткие условные конструкции. Так что мы могли бы написать такой блок, выполняющий роль switch-case:
x = 5
switch = (
(1 == x and ‘Foo’) or
(2 == x and ‘Bar’) or
(3 == x and ‘Buz’) or
None
)
print(switch)
Минусы такого подхода: легко запутаться и допустить ошибку. Читать такой код даже самому через какое-то время будет сложно. Ну и надо сразу предупредить, что данный способ будет работать только в том случае, если второй аргумент оператора and
всегда будет содержать True
-выражение, иначе этот блок будет пропускаться.
Кстати, в PHP можно использовать подобный подход:
<?php
$x = 2;
(
(1 == $x and $switch = ‘Foo’) or
(2 == $x and $switch = ‘Bar’) or
(3 == $x and $switch = ‘Buz’) or
($switch = null )
);
print($switch);
Но это так, оффтоп, да и нецелесообразно. Но ведь можем? Могём!
Еще бывают варианты на исключениях и другая экзотика. Но это все уже персонально, может быть логика где такие варианты могут быть оправданны.
Ну и в конце-то концов, никто не отменял просто цепочку if-elif-else
. Такая конструкция может оказаться сильно выгоднее как по скорости, так и по читаемости.
Материалы по теме
https://docs.python.org/3/faq/design.html#why-isn-t-there-a-switch-or-case-statement-in-python