Rozdział 13 · Automatyzacja z funkcjami

Debug Lab: typowe błędy funkcji

Zapomniany return return wewnątrz pętli w pierwszej iteracji, losowo zmutowany argument — dziesięć błędów, które powtarzają się wielokrotnie w rzeczywistym kodzie.

Dziesięć typowych błędów związanych z funkcjami — każdy z przykładem i wyjaśnieniem. Pierwsze dwa były już omówione osobno (§13.1, §13.13) — tutaj dla kompletności listy, zwięźle; pozostałe osiem — szczegółowo.

1 · Losowe wywołanie przed definicją
bug.py
greet()

def greet():
    print('Cześć')

W momencie połączenia greet() nie jest jeszcze zdefiniowany — Python wykonuje kod od góry do dołu i dzieje się NameError zanim łańcuch znaków def zdąży go wykonać.

2 · Niewidoczny def w def
bug.py
def outer():
    ...

    def helper():
        ...

Wcięcie definiuje zagnieżdżanie. Jeśli helper jest wcięte wewnątrz outer, nie będzie widoczne z zewnątrz jako niezależna funkcja, tylko przez outer.

3 · Funkcja zawsze zwraca None
bug.py
def calculate(x):
    print(x * 2)

result = calculate(5) + 1

calculate się drukuje, ale nie wraca – result staje się None i None + 1 powoduje TypeError.

4 · Nie wszystkie ścieżki funkcji zwracają wartość
bug.py
def sign(number):
    if number > 0:
        return 'positive'
    # a co jeśli number <= 0?

Dla liczb ujemnych i zerowych funkcja osiąga koniec bez return — i bezszelestnie zwraca None.

5 · return w pętli przy pierwszej iteracji
bug.py
def contains_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            return True
        else:
            return False

return w rozgałęzieniu else jest już wyzwalane na pierwszym elemencie — nie wszystkie liczby są zaznaczone. Poprawnie: return True wewnątrz if, a return False — po całej pętli, z tym samym wcięciem co for.

6 · return pomieszane z break
bug.py
def find_first_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            break   # a nie return n!

break po prostu zatrzymuje cykl — n trzeba jeszcze jawnie zwrócić oddzielnym wierszem po cyklu. return natychmiast wychodzi z CAŁEJ funkcji, break — tylko z najbliższego cyklu.

7 · Niespodziewana mutacja przekazanej listy
bug.py
def sorted_names(names):
    names.sort()
    return names

names.sort() mutuje lista kodu wywołującego w miejscu. Jeśli nie jest to przewidziane w planie, bezpieczniej return sorted(names) — nowy lista, oryginał pozostaje nietknięty.

8 · Zapomnieli skopiować przed zmianą
bug.py
def normalize_items(items):
    items[0] = items[0].strip()
    return items

Jeśli dane źródłowe trzeba zachować niezmienione, zacznij od items = items.copy() — ale pamiętaj o powierzchowności kopii (rozdział 11, §11.10) dla struktur zagnieżdżonych.

9 · Stan globalny utrudnia testowanie
bug.py
score = 0

def add_point():
    global score
    score += 1

Działa, ale trudniej jest przetestować taką funkcję osobno — jej wynik zależy od ukrytej zmiennej globalnej. def add_point(score): return score + 1 jest testowany na jednej linii, bez żadnej konfiguracji.

10 · Parametr zacienia nazwę inline
bug.py
def total(list):
    return sum(list)

list — nazwa wbudowanego typu. Zastąpienie jej parametrem działa, ale myli czytelnika i łamie dostęp do prawdziwego list() wewnątrz tej funkcji. Lepiej: def total(numbers):

Metoda debugowania funkcji

  • Drukuje funkcja, czy zwraca? Sprawdź na papierze, zanim przeczytasz kod.
  • Sprawdzono WSZYSTKIE ścieżki wykonywania funkcji, w tym niejawny „koniec bez return"?
  • return w pętli — czy na pewno powinien zadziałać w tej iteracji?
  • Czy funkcja celowo mutuje argument przekazany? Jeśli nie, skopiuj go.
  • Czy nazwa parametru jest mylona z wbudowaną nazwą Python?
Praktyka: znajdujemy i poprawiamy błędy w funkcjach
interaktywny laptop bezpośrednio w przeglądarce – Python 3.14 przez Pyodide, bez instalacji
Otwórz praktykę →