CodeGym /Kurse /Python SELF DE /Standardfehler

Standardfehler

Python SELF DE
Level 20 , Lektion 1
Verfügbar

7.1 Falsche Verwendung von Ausdrücken als Standardwerte für Funktionsargumente

Du hast schon viel gelernt, lass uns die häufigsten Fehler von Anfängern durchgehen – besser aus den Fehlern anderer lernen als aus den eigenen, oder?

Python ermöglicht es, dass eine Funktion optionale Argumente haben kann, indem für sie ein Standardwert festgelegt wird. Das ist natürlich eine sehr praktische Funktion der Sprache, kann aber zu unerwünschten Konsequenzen führen, wenn der Typ eines solchen Werts veränderlich ist. Betrachten wir zum Beispiel die folgende Funktionsdefinition:


def foo(bar=[]):  # bar ist ein optionales Argument 
# und standardmäßig eine leere Liste.
    bar.append("baz")  # Diese Zeile könnte problematisch sein...
    return bar

Ein häufiger Fehler in diesem Fall ist zu denken, dass der Standardwert für das optionale Argument jedes Mal gesetzt wird, wenn die Funktion ohne einen Wert für dieses Argument aufgerufen wird.

Im obigen Code könnte man zum Beispiel annehmen, dass durch wiederholten Aufruf der Funktion foo() (also ohne Angabe eines Wertes für das Argument bar), sie immer ["baz"] zurückgibt, da angenommen wird, dass jedes Mal, wenn foo() aufgerufen wird (ohne Angabe des Arguments bar), bar auf [] gesetzt wird (d.h. eine neue leere Liste).

Aber schauen wir uns an, was tatsächlich passiert:


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

Warum fügt die Funktion weiterhin den Wert baz zur bestehenden Liste hinzu, jedes Mal, wenn foo() aufgerufen wird, anstatt jedes Mal eine neue Liste zu erstellen?

Die Antwort auf diese Frage erfordert ein tieferes Verständnis dafür, was bei Python „unter der Haube“ passiert. Nämlich: Der Standardwert für eine Funktion wird nur einmal initialisiert, und zwar bei der Definition der Funktion.

Folglich wird das Argument bar standardmäßig (d.h. mit einer leeren Liste) nur dann initialisiert, wenn foo() zum ersten Mal definiert wird. Nachfolgende Aufrufe von foo() (also ohne Angabe des Arguments bar) werden weiterhin dieselbe Liste verwenden, die für das Argument bar beim ersten Definieren der Funktion erstellt wurde.

Zur Info: Ein häufig genutzter „Workaround“ für diesen Fehler ist die folgende Definition:


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 Falsche Verwendung von Klassenvariablen

Schauen wir uns folgendes Beispiel an:


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

Sieht alles in Ordnung aus.

Jetzt weisen wir das Feld x der Klasse B einen Wert zu:


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

Alles wie erwartet.

Jetzt ändern wir die Klassenvariable A:


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

Seltsam, wir haben doch nur A.x geändert. Warum hat sich C.x auch geändert?

In Python werden Klassenvariablen als Dictionaries behandelt und folgen dem, was oft als Method Resolution Order (MRO) bezeichnet wird. Im obigen Code wird das Attribut x, wenn es nicht in der Klasse C gefunden wird, in seinen Basisklassen (nur A im obigen Beispiel, obwohl Python Mehrfachvererbung unterstützt) gefunden.

Mit anderen Worten, C hat keine eigene Eigenschaft x, die von A unabhängig ist. Daher sind Verweise auf C.x tatsächlich Verweise auf A.x. Dies kann Probleme verursachen, wenn man solche Fälle nicht richtig berücksichtigt. Beim Lernen von Python sollte man besonderes Augenmerk auf Klassenattribute und den Umgang mit ihnen legen.

7.3 Falsche Angabe von Parametern für den Ausnahmeblock

Angenommen, du hast den folgenden Codeblock:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # Um beide Ausnahmen zu erfassen, richtig?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

Das Problem hier ist, dass das except-Statement keine Liste von Ausnahmen auf diese Weise akzeptiert. Daher wird im obigen Code die Ausnahme IndexError nicht vom except-Statement abgefangen; stattdessen endet die Ausnahme damit, an einen Parameter mit dem Namen IndexError gebunden zu werden.

Wichtig! Solcher Code kann in Beispielen im Internet vorkommen, da er in Python 2.x verwendet wurde.

Der korrekte Weg, um mehrere Ausnahmen mit einem except-Statement abzufangen, besteht darin, den ersten Parameter als Tupel anzugeben, das alle zu fangenden Ausnahmen enthält. Außerdem kannst du aus Gründen der besseren Lesbarkeit das Schlüsselwort as verwenden, zum Beispiel so:


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