Ostatnio na blogu pojawił się post „Czego nie powinniśmy robić w Pythonie? #1″ i już w trakcie jego pisania podjąłem decyzję, że wpisów z tej serii pojawi się więcej. Dziś, zgodnie z tą decyzją, pojawia się kolejny post. Pokażę w nim kolejne trzy rzeczy, których w Pythonie powinniśmy unikać.
Pomijając zbędny wstęp, zacznijmy!
Konfliktowe nazwy zmiennych
Do faktu, że poprawne nazywanie zmiennych jest niepodważalną podstawą czystego kodu nie muszę raczej nikogo przekonywać. W Pythonie powinniśmy zwrócić uwagę na to jak nazywamy zmienne, ponieważ możemy nieopatrznie nadpisać funkcje z biblioteki standardowej. Zerknij na to:
1 2 3 | list = [1, 2, 3] print(list) |
Taki kod oczywiście zadziała. A teraz wyobraźmy sobie, że pod takim kodem dojdzie 1000 linii i zajdzie potrzeba otrzymania listy liczb, np tak:
1 2 3 4 5 | list = [1, 2, 3] # 1000 lines numbers = list(range(10)) |
musimy tak zrobić, bo funkcja range nie zwraca listy. Szczęśliwi uruchamiamy nasz kod i… Dostajemy błąd
Traceback (most recent call last):
File "/home/mmazurek/PycharmProjects/examples/example.py", line 5, in
numbers = list(range(10))
TypeError: 'list' object is not callable
i pojawia się „wtf?!”.
Jak się pewnie domyślacie problem jest w tym, że nadpisaliśmy w pierwszej linii wbudowaną funkcję list obiektem listy, a następnie (po 1000 innych linijkach) próbujemy użyć naszej listy jako obiektu callable, a lista przecież nim nie jest. Rozwiązaniem jest zmiana nazwy zmiennej znajdującej się w pierwszej linii.
Takie błędy zdarzają się dość często. Inne funkcje, które bywają przykrywane to: type, dict, range itp.
Znalezienie takich błędów może nie być takie proste, szczególnie jeśli od deklaracji do użycia mija wiele linii kodu.
Czekaj, stop!
Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:Jeśli to Cię interesuje to zapraszam również na swoje social media.
Jak i do ewentualnego postawienia mi kawy :)
Nieużywanie metody get
Zerknijmy na taki kod:
1 2 3 4 5 6 7 8 | dictionary = {"key": "val"} data = "" if "key" in dictionary: data = dictionary["key"] print(data) |
I on sobie spokojnie zadziała. Jednak polecanym sposobem jest używanie metody .get(key, [default]) czyli np.:
1 2 3 4 5 | dictionary = {"key": "val"} data = dictionary.get("key", "") print(data) |
Zwraca nam ona wartość klucza, jeśli ten istnieje w słowniku. Jeśli taki nie istnieje, to zwraca swój drugi argument, czyli domyślną wartość. Nie dokopałem się do żadnego PEPa, który by nakazywał tę konwencję, ale jest ona zdecydowanie przejrzystsza i bardziej Python’owa. Jeśli Ty znalazłeś PEPa mówiącego o tym – podziel się w komentarzu.
Zła kolejność łapania wyjątków
Zerknij na taki kod:
1 2 3 4 5 6 7 8 9 10 | def super_dangerous_func(): raise KeyError() try: super_dangerous_func() except Exception as x: print("General Exception") except KeyError as ke: print("KeyError") |
Co on wypisze? Oczywiście że „General Exception”.
Ogólnie zasada konstruowania bloków except jest taka, żeby obsługiwać wyjątki od najbardziej szczegółowego do najbardziej ogólnego, czyli dokładnie odwrotnie jak jest na przykładzie. Powód jest prosty – prawdopodobnie, w zależności od wyjątku, będziemy chcieli inaczej na niego zareagować. Jeśli nie będziemy respektować kolejności, stracimy tę możliwość.
Oczywistym jest, że możemy łapać tylko wyjątek Exception i już w jego bloku sprawdzać, jakiej instancji jest zmienna x, ale to wypala programistom oczy.
Poprawnym przykładem byłby kod:
1 2 3 4 5 6 7 8 9 10 | def super_dangerous_func(): raise KeyError() try: super_dangerous_func() except KeyError as ke: print("KeyError") except Exception as x: print("General Exception") |
który wypisze po prostu „KeyError”.
Zmierzając do brzegu
Ło, druga cześć za nami. Trzecia się pojawi na bank, stay tuned!
Mateusz Mazurek
Bardzo fajne są takie krótkie wpisy z dużą dawką wiedzy merytorycznej. Myślę, że dzięki takim wpisom porcjujesz informacje, które łatwo można przyswoić (3 – 5 minut tygodniowo to niewiele czasu). Przykłady w kodzie są bardzo proste do zrozumienia i wydaje mi się, że byłyby jeszcze prostsze gdyby pod nimi był output z konsoli ;)
Ok, będzie! :D
Co do metody get to się nie zgodzę, uważam, że powinno się ją stosować tylko wtedy, gdy chcemy mieć jakąś wartość w przypadku braku elementu. Nie bez powodu są przecież kwadratowe nawiasy, jestem także zdania, że sa one bardziej przejrzyste, ale to kwestia przyzwyczajenia. Sam artykuł ciekawy i liczę na więcej :D
Dzięki za komentarz, będzie więcej! A co do get() to jeśli masz pewność że w słowniku klucz będzie to oczywiście get() jest zbędne:) tu miałem na mysli sytuację że może nie być:)
Strasznie fajny artykuł. Myślę że przesłanianie zmiennych jest tak oczywiste że nawet początkujący programiści nie popełniają tego błędu. Czy się mylę :-D? Co do wyjątków i obsługi błędów to też problem wydaje mi się bardzo błachy i nie wyobrażam sobie żeby ktoś robił tego typu błędy. Jednak fajnie że to wszystko tutaj poruszasz Mateusz. Będę wyczekiwał trzeciej części tej serii artykułów. Pozdrawiam :-).
Oj robią takie błędy… :D dzięki! Na 100% będzie trzecia część:)
get pozwala bezpiecznie zaglądać do słownika, w przeciwnym wypadku otrzymamy KeyErrror:
dictionary = {„a”: „b”}
print(dictionary.get(„x”))
print(dictionary.get(„x”, „y”))
print(dictionary[„x”])
None
y
Traceback (most recent call last):
File „<string>”, line 8, in <module>
KeyError: 'x’