Полный гайд по реализации 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