CodeGym /Kursy /Python SELF PL /Błędy standardowe

Błędy standardowe

Python SELF PL
Poziom 20 , Lekcja 1
Dostępny

7.1 Niepoprawne użycie wyrażeń jako wartości domyślnych dla argumentów funkcji

Już się wielu nauczyłeś, więc przeanalizujmy najczęstsze błędy początkujących – lepiej uczyć się na cudzych błędach niż na swoich – prawda?

Python pozwala na określenie, że funkcja może mieć opcjonalne argumenty przez nadanie im wartości domyślnych. To jest oczywiście bardzo wygodna cecha języka, ale może prowadzić do nieprzyjemnych konsekwencji, jeśli typ tej wartości jest modyfikowalny. Na przykład, rozważmy następującą definicję funkcji:


def foo(bar=[]):  # bar - to opcjonalny argument
# i domyślnie jest pustą listą.
    bar.append("baz")  # ta linia może być problematyczna...
    return bar

Popularnym błędem w tym przypadku jest myślenie, że wartość opcjonalnego argumentu będzie ustawiana na wartość domyślną za każdym razem, gdy funkcja zostanie wywołana bez wartości dla tego argumentu.

W powyższym kodzie, na przykład, można przypuszczać, że ponowne wywołanie funkcji foo() (czyli bez wskazywania wartości dla argumentu bar) zawsze zwróci ["baz"], ponieważ zakłada się, że za każdym razem, gdy wywołuje się foo() (bez wskazania argumentu bar), bar jest ustawiane na [] (czyli nową pustą listę).

Ale zobaczmy, co się naprawdę dzieje:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

Dlaczego funkcja nadal dodaje wartość baz do istniejącej listy za każdym razem, gdy wywołuje się foo() zamiast tworzyć nową listę za każdym razem?

Odpowiedzią na to pytanie będzie głębsze zrozumienie, co dzieje się w Pythonie „pod maską”. Mianowicie: wartość domyślna dla funkcji jest inicjalizowana tylko raz, w momencie definicji funkcji.

W ten sposób, argument bar jest inicjalizowany domyślnie (czyli pustą listą) tylko wtedy, gdy foo() jest definiowana po raz pierwszy, ale kolejne wywołania foo() (czyli bez wskazania argumentu bar) będą nadal korzystać z tej samej listy, która została utworzona dla argumentu bar w momencie pierwszej definicji funkcji.

Dla informacji, popularną „obejściem” dla tego błędu jest następująca definicja:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 Niepoprawne użycie zmiennych klasy

Przeanalizujmy następny przykład:


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

Wszystko wydaje się w porządku.

Teraz przypiszmy wartość do pola x w klasie B:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

Wszystko zgodnie z oczekiwaniami.

A teraz zmienimy zmienną klasy A:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

Dziwne, przecież tylko zmieniliśmy A.x. Dlaczego więc C.x też się zmieniło?

W Pythonie zmienne klasy są traktowane jak słowniki i postępują zgodnie z tym, co często nazywa się Porządkiem rozwiązywania metod (MRO). W związku z tym, w powyższym kodzie, ponieważ atrybut x nie został znaleziony w klasie C, to zostanie znaleziony w jego klasach bazowych (w powyższym przykładzie tylko A, chociaż Python obsługuje dziedziczenie wielokrotne).

Innymi słowy, C nie ma własnej właściwości x, niezależnej od A. Odwołania do C.x w rzeczywistości są odwołaniami do A.x. Może to powodować problemy, jeśli się nie uwzględni takich przypadków należycie. Przy nauce Pythona zwróć szczególną uwagę na atrybuty klasy i pracę z nimi.

7.3 Niepoprawne wskazywanie parametrów dla bloku wyjątków

Załóżmy, że mamy następujący fragment kodu:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # Chcemy złapać oba wyjątki, prawda?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

Problem polega na tym, że stwierdzenie except nie przyjmuje listy wyjątków podanych w ten sposób. W rezultacie w powyższym kodzie wyjątek IndexError nie jest przechwytywany przez stwierdzenie except; zamiast tego wyjątek kończy się przypisaniem do parametru o nazwie IndexError.

Ważne! Taki kod możesz spotkać w przykładach w internecie, ponieważ był używany w Pythonie 2.x.

Prawidłowy sposób przechwytywania wielu wyjątków za pomocą stwierdzenia except polega na wskazaniu pierwszego parametru jako krotki zawierającej wszystkie wyjątki, które chcemy przechwycić. Dla lepszej czytelności możesz użyć słowa kluczowego as, na przykład tak:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION