Początkujący programiści Pythona, przychodzący z innych języków, wcześniej czy później trafiają na problem braku implementacji switch/case w tym języku. W tym poście omówię alternatywy dla tej popularnej konstrukcji. Omówię też nową instrukcję Pythona 3.10: match/case.
Wszystkie Pythonowe przykłady poniżej są pisane z wykorzystaniem paczki ipython - interaktywnej powłoki dla Pythona, o poszerzonych możliwościach, m.in. podpowiadaniu składni.
Czym jest switch/case?
Switch/case jest instrukcją umożliwiającą porównanie wartości zmiennej z wieloma warunkami i wybranie pasującego. W JavaScript wygląda to tak:
var HTTP_METHOD = "GET";
switch (HTTP_METHOD){
case "POST":
case "PATCH":
console.log("POST or PATCH");
break;
case "GET":
console.log("GET");
break;
default:
console.log("all the others");
}Jak widać z przykładu, jest to prosta instrukcja o bardzo szerokim zastosowaniu. Kod z wykorzystaniem switch case jest zdecydowanie czytelniejszy niż gdyby to wszystko opakować w if/else.
Przykład 1. Wykorzystanie if/else.
W Pythonie, niestety instrukcji switch/case nie ma. Jedną z alternatyw jest rozbudowany if/else.
In [2]: HTTP_METHOD = "GET"
In [3]: if HTTP_METHOD == "POST":
...: print("POST")
...: elif HTTP_METHOD == "GET":
...: print("GET")
...: else:
...: print("all the others")
...:
GETJak widać, nie jest to ani eleganckie, ani na zbytnio czytelne.
Przykład 2. Wykorzystanie słowników.
Na ratunek przychodzą słowniki.
In [5]: HTTP_METHOD = "GET"
In [6]: methods = {
...: "GET": lambda: (
...: print("GET")
...: ),
...: "POST": lambda: (
...: print("GET")
...: )
...: }
In [7]: methods.get(HTTP_METHOD, lambda: (print("all the others")))()
GETPowyższy kod jest zdecydowanie bardziej czytelny niż przykład pierwszy. Jest też dużo zdecydowanie łatwiejszy do rozbudowy. Niestety, samo wywołanie funkcji jest.. zwyczajnie brzydkie, już nie wspominając o czytelności.
Ładniejsza wersja:
In [9]: HTTP_METHOD = "GET"
...: def handle_http_method(method):
...: return {
...: "GET": lambda: (
...: print("GET")
...: ),
...: "POST": lambda: (
...: print("GET")
...: )
...: }.get(method, lambda: (print("all the others")))()
In [10]: handle_http_method(HTTP_METHOD)
GET
In [11]: handle_http_method("PATCH")
all the othersJak widać, kod jest bardzo podobny, słownik został tylko opakowany w funkcje. Takie podejście nie tylko zwiększa czytelność, ale zdecydowanie upraszcza wywołania.
Można to zrobić jeszcze przyjaźniej, lepiej, a przede wszystkim czytelniej. Z pomocą przychodzi Python 3.10 i instrukcja match/case (PEP 634 https://docs.python.org/3/whatsnew/3.10.html#pep-634-structural-pattern-matching)
Python 3.10 i dopasowywanie wzorców strukturalnych.
Najnowsza wersja Pythona wprowadziła instrukcję match/case umożliwiającą uzyskanie efektu podobnego jak w innych językach programowania przy zastosowaniu switch/case. Ale nie tylko — nowa funkcjonalność umożliwia bardziej rozbudowane dopasowania. Więcej można przeczytać w dokumentacji: https://docs.python.org/3/whatsnew/3.10.html#pep-634-structural-pattern-matching.
Różnice pomiędzy implementacją znaną z innych języków:
- pierwsza i podstawowa: zamiast "switch" słowo kluczowe: "match"
- symbol wieloznaczny, stosowany w przypadku braku dopasowania, w Pythonie to "_", a nie "default".
Najprostsze wykorzystanie, wymienione dokładnie w dokumentacji:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"Python również pozwala na łączenie wielu wzorców za pomocą instrukcji "|" (or) - podobnie jak case dla PATCH i POST w przykładowym JavaScripcie na początku posta.
case 401 | 403 | 404:
return "Not allowed"Ciekawe są złożone wzory:
match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")Tutaj nawet jest zastosowany symbol uniwersalny: "_".
Oprócz tego można również zastosować "strażnika":
match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")Pierwszy case zostanie wykonany tylko wtedy gdy x == y. Jakie to daje możliwości!
Powyższe przykłady można znaleźć w dokumentacji: https://docs.python.org/3/whatsnew/3.10.html#pep-634-structural-pattern-matching - polecam jej lekturę, tutaj ograniczyłem się do podstawowych zastosowań.
Podsumowanie
Jak widać, Python 3.10 przynosi bardzo ciekawą funkcjonalność w postaci instrukcji match/case. Python zaskoczył pozytywnie, nie kopiując istniejącego rozwiązania, a budując własne, zdecydowanie bardziej rozbudowane.
Polecam aktualizacje do wersji 3.10. Na MacOS jest to bardzo proste.
$ brew update
$ brew install [email protected]
$ brew unlink python3
$ brew link --force [email protected]
$ python3 --versionPs. Spodobał Ci się post? Udostępnij go na swoich kanałach.