Критерии выбора между if-elif-else и match-case в Python

Что лучше? Что читабельнее? Как правильнее?

Рассмотрим, например, часть кода с github пректа.

Исходные данные:

dict_url = {"google": 'https://www.google.com/search?q=',
                "yandex": 'https://ya.ru/search/?text=',
                "bing_list": ['https://www.bing.com/search?q=', '&form=QBLH'],
                "yahoo": 'https://ru.search.yahoo.com/search;?p=',
                "nova_rambler": 'https://nova.rambler.ru/search?query=',
                "duckduckgo_list": ['https://duckduckgo.com/?origin=funnel_home_website&t=h_&q=', '&ia=web'],
                "brave_list": ['https://search.brave.com/search?q=', '&source=brave.com'],
                "perplexity": 'https://www.perplexity.ai/search/',
                "phind": 'https://www.phind.com/',
                "tineye": 'https://tineye.com/search/'}
names_keys_list = ["google", "yandex", "bing_list", "phind", "tineye"]

Проверка и ветвление через match-case:

match search_selection:
    case 0:
        for value in dict_url.values():
            try:
                response = rq.get(value + search_query)
                print(response.url)
            except Exception:
                response = rq.get(value[0] + search_query + value[1])
                print(response.url)
    case _:
        url_search_engin = dict_url.get(f"{names_keys_list[search_selection - 1]}")
        try:
            response = rq.get(url_search_engin + search_query)
            print(response.url)
        except Exception:
            response = rq.get(url_search_engin[0] + search_query + url_search_engin[1])
            print(response.url)

Та же проверка, но через if:

if search_selection == 0:
    for value in dict_url.values():
        try:
            response = rq.get(value + search_query)
            print(response.url)
        except Exception:
            response = rq.get(value[0] + search_query + value[1])
            print(response.url)

if search_selection != 0:
    url_search_engin = dict_url.get(f"{names_keys_list[search_selection - 1]}")
    try:
        response = rq.get(url_search_engin + search_query)
        print(response.url)
    except Exception:
        response = rq.get(url_search_engin[0] + search_query + url_search_engin[1])
        print(response.url)

Ответы (2 шт):

Автор решения: CrazyElf

Вообще дело вкуса. Но обычно если варианта всего два и они по сути сильно разные, то лучше через if ... else:

if choice == first_choice:
...
else:
...

А если вариантов выбора много и у вас Питон той версии, которая уже поддерживает match-case, то конечно лучше выбрать match, чем толпу elif, в которой легко запутаться.

Кроме прочего, хорошо бы большие блоки рефакторить - выносить в отдельные методы, которые просто вызывать в ветках, чтобы у вас там не было простыни кода, за которой можно потерять суть ветвления.

Ну и когда у вас зарефакторены отдельные методы, то можно как вариант применить ветвление через словарь:

choices = {
'1': func1,
'2': func2,
'3': func3
}

func = choices.get(choice, error)
func()

Хотя если этим функциям нужны аргументы, то всё будет не так уже просто. Проще тогда уж через match всё-таки.

→ Ссылка
Автор решения: Vitalizzare

Конструкция match-case добавлена в Python не для замены if-elif-else в случае выбора из нескольких значений, а для структурного сопоставление объектов по образцу, см. PEP 635. Она уместна, когда логика действий зависит от типа объекта и наличия в нем определённых свойств.

В приведённом примере использование match-case в указанном месте избыточно, достаточно if-else:

if search_selection == 0:
    ...
else:
    ...    

При этом я бы обратил внимание на конструкцию try-except, которая применена неправильно. Там, как я понимаю, должна быть проверка типа передаваемого значения value. А для этого лучше всего подойдёт match-case.

Сравним все три конструкции, начиная с неправильного использования try-except:

try:
    # ожидается, что value - это строка
    response = rq.get(value + search_query)
    print(response.url)
except Exception:
    # ожидается, что value - это список из двух строк
    response = rq.get(value[0] + search_query + value[1])
    print(response.url)

Вместо try-except было бы правильно применить if-else:

if isinstance(value, str):
    response = rq.get(value + search_query)
elif (
    isinstance(value, list | tuple)
    and len(value) == 2
    and isinstance(value[0], str)
    and isinstance(value[1], str)
):
    response = rq.get(value[0] + search_query + value[1])
else:
    raise ValueError(f'Incorrect {value=}')

print(response.url)

Но с появлением match-case это можно выразить более лаконично:

match value:
    case str(search_url):
        response = rq.get(search_url + search_query)
    case [str(search_url), str(parameters)]:
        response = rq.get(search_url + search_query + parameters)
    case _: 
        raise ValueError(f'Incorrect {value=}')

print(response.url)

Здесь search_url и parameters принимают значения тех или иных свойст объекта value в зависимости от кейса:

  • Если value - это строка, то search_url равно её содержимому. Можно обойтись case str(): response = rq.get(value + search_query). Но за счёт именования saerch_url легче понять при повторном чтении, что там содержится, и более однотипно выразить логику в разных кейсах.
  • Если value - последовательность из двух строк, то их содержимое изымается и используется под именами search_url и parameters. При желании, можно обойтись case [str(), str()]: ..., обращаясь к элементам value по индексу.
  • Все другие случаи обрабатываются как ошибка ValueError.

См. ещё пример, где использование match-case уместно

→ Ссылка