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.
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ć.
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.
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.
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.
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.
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.
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.
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.
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.
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?