Wróć na górę
Artykuły / Python

Najczęstsze błędy w Pythonie: środowisko, typy i limity zasobów

Autor: Connor Walsh 22 sierpnia 2024 ok. 15 min czytania

Czytanie i rozumienie Tracebacka to codzienność w pracy z Pythonem. Niezależnie od tego, czy czyścisz dane, czy odpalasz kontener na serwerze, czerwony blok logów zwykle zawiera wszystkie potrzebne wskazówki.

Poniżej zebraliśmy najczęstsze typy błędów z praktyki, pogrupowane nie alfabetycznie, ale według kontekstu: od problemów środowiskowych, przez składnię i typy danych, po limity zasobów i błędy stricte biznesowe.

I. Środowisko i zależności

Ten zestaw błędów pojawia się najczęściej po ściągnięciu nowego projektu, zmianie środowiska albo przy budowaniu obrazów Dockera. Kod może być poprawny – to środowisko nie jest jeszcze gotowe.

1. ModuleNotFoundError / ImportError

Typowa sytuacja: moduł zewnętrzny lub własny pakiet nie daje się zaimportować.

  • czy na pewno aktywowałeś właściwe virtualenv?
  • czy requirements.txt został zainstalowany w tym środowisku?
  • czy katalog z własnym modułem jest w sys.path?
bash
# w aktywnym virtualenv
pip install requests
Python
import sys
sys.path.append("/path/to/module_directory")
import some_custom_module
2. FileNotFoundError

Zwykle wychodzi na jaw dopiero w środowisku innym niż lokalne – gdy ścieżki względne rozumiane są inaczej niż na Twojej maszynie. Tam, gdzie to możliwe, korzystaj z absolutnych ścieżek lub buduj je względem katalogu projektu.

II. Składnia i zasięg zmiennych

W dobrym IDE większość takich błędów zobaczysz jeszcze przed uruchomieniem skryptu. Gdy jednak edytujesz plik na serwerze w prostym edytorze, łatwo o wpadkę.

SyntaxError i IndentationError

Brak dwukropka, niezamknięty nawias, mieszanie spacji z tabami – Python jest bardzo wrażliwy na poprawną strukturę kodu. Warto trzymać się konwencji 4 spacji na poziom wcięcia i używać autoformatowania (Black, Ruff itd.).

NameError i UnboundLocalError

Pierwszy pojawia się, gdy odwołujesz się do nazwy, która nie istnieje w danym zasięgu. Drugi – gdy interpreter uznaje, że tworzysz zmienną lokalną, ale próbujesz jej użyć, zanim zostanie przypisana.

Python
x = 20


def test_function():
    # Bez global x poniżej Python uzna x za zmienną lokalną
    global x
    print(x)
    x = 10


test_function()
III. Dane i typy: błędy przy pracy z wejściem

Tu najczęściej potykamy się przy integracjach z API, parsowaniu JSON-ów czy sprzątaniu danych analitycznych.

TypeError i ValueError

TypeError oznacza, że operujemy na niekompatybilnych typach (np. dodawanie str + int albo próba wywołania liczby jak funkcji). ValueError pojawia się, gdy typ niby pasuje, ale wartość nie – np. konwersja nienumerycznego napisu na int.

Python
try:
    num = int("abc")
except ValueError:
    print("Nie udało się przekształcić na liczbę całkowitą")
IndexError i KeyError

Pierwszy oznacza wyjście poza zakres listy, drugi – brak klucza w słowniku. Przy danych z zewnątrz lepiej używać dict.get() z wartością domyślną niż zakładać, że klucz zawsze istnieje.

Python
my_dict = {"a": 1, "b": 2}
print(my_dict.get("c", 0))  # brak KeyError, zwraca 0
AttributeError

Próba użycia atrybutu lub metody, której obiekt nie posiada (np. lista z .add() zamiast .append()). Zwykle oznacza to błędne założenie co do typu obiektu zwracanego przez inną funkcję.

UnicodeDecodeError

Pojawia się przy czytaniu tekstu w innym kodowaniu niż domyślne. Rozwiązanie: jawnie wskazać encoding.

Python
byte_data = b"\xd0\xbf\xd1\x80\xd0\xb8..."
text = byte_data.decode("utf-8")
IV. Limity zasobów i obliczeń

Gdy operujesz na dużych zbiorach danych, rekurencji lub ekstremalnych wartościach, wchodzą do gry ograniczenia pamięci i stosu.

MemoryError

Oznacza najczęściej próbę wczytania do pamięci zbyt dużej struktury danych. W praktyce lepiej przetwarzać dane strumieniowo (generatory, chunkowanie) zamiast budować gigantyczne listy.

Python
# zamiast: huge_list = [i for i in range(10**10)]
huge_generator = (i for i in range(10**10))
RecursionError

Python ma domyślny limit głębokości rekurencji (~1000). Można go podnieść przez sys.setrecursionlimit(), ale znacznie lepszym rozwiązaniem jest przepisanie algorytmu na wersję iteracyjną.

OverflowError i ZeroDivisionError

W czystym Pythonie liczby całkowite są"bez limitu”, ale w bibliotekach opartych na C (NumPy itd.) typy takie jak int32/int64 mają swój sufit. ZeroDivisionError to z kolei klasyka – dzielenie przez zero.

V. Logika biznesowa i błędy wykonywania
AssertionError

Zwykle pojawia się podczas developmentu, gdy używasz assert, aby upewnić się, że"to nigdy nie powinno się wydarzyć”. W produkcji możesz włączyć Pythona z -O, żeby zignorować asercje dla niewielkiego zysku wydajności.

RuntimeError

Gdy nie ma lepszego, bardziej precyzyjnego typu wyjątku, wiele bibliotek używa RuntimeError jako"ogólnego” sygnału, że coś poszło nie tak w logice wykonania (np. nieoczekiwany stan maszyny).

VI. Trzy pytania kontrolne na koniec
  • Czy na pewno jesteś w tym virtualenv?
    Przy ModuleNotFoundError zanim zrobisz kolejne pip install, sprawdź, czy interpreter w IDE/terminalu to ten sam, do którego instalowałeś paczki.
  • Czy kod zakłada, że dane z zewnątrz są"idealne”?
    Z API lub plików zawsze może przyjść null, zły format lub brak klucza – używaj .get(), try/except ValueError i sensownych domyślnych wartości.
  • Czytasz Traceback od dołu?
    Górne linie często należą do frameworka. Prawdziwa przyczyna i numer linii Twojego kodu zwykle siedzą w ostatnich linijkach Tracebacka.
Poprzedni artykuł "Przepisałem kernel na Rust i nic nie przyspieszyło” – o pułapkach optymalizacji między językami
Następny artykuł To jest najnowszy artykuł