Критерии выбора между 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 шт):
Вообще дело вкуса. Но обычно если варианта всего два и они по сути сильно разные, то лучше через 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 всё-таки.
Конструкция 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.