Heim >Backend-Entwicklung >Python-Tutorial >Zehn häufige Fehler, die Python-Programmierer machen
Ob beim Lernen oder Arbeiten, Menschen werden Fehler machen. Obwohl die Syntax von Python einfach und flexibel ist, gibt es dennoch einige große Fallstricke. Wenn Sie nicht aufpassen, können sowohl Anfänger als auch erfahrene Python-Programmierer stolpern. Dieser Artikel teilt Ihnen 10 häufige Fehler mit. Freunde in Not können sich darauf beziehen
Häufiger Fehler 1: Falsche Verwendung eines Ausdrucks als Standardparameter einer Funktion
In Python können wir einen Parameter für a festlegen Funktion Der Parameter legt einen Standardwert fest, wodurch der Parameter optional ist. Obwohl dies eine nette Sprachfunktion ist, kann sie auch zu verwirrenden Situationen führen, wenn der Standardwert ein veränderlicher Typ ist. Schauen wir uns die folgende Python-Funktionsdefinition an:
>>> ,
... bar.append("baz") # Aber wir werden später sehen, dass diese Codezeile Probleme verursachen wird.
... return bar
Ein häufiger Fehler, den Python-Programmierer machen, besteht darin, davon auszugehen, dass bei jedem Aufruf einer Funktion dieser optionale Parameter dies tut, wenn kein Wert für den optionalen Parameter übergeben wird auf den angegebenen Standardwert gesetzt werden. Im obigen Code denken Sie vielleicht, dass ein wiederholter Aufruf der Funktion foo() immer „baz“ zurückgeben sollte, da standardmäßig jedes Mal, wenn die Funktion foo() ausgeführt wird (der Wert der bar-Variablen ist nicht angegeben), der bar Variable wird auf [ ] gesetzt (d. h. eine neue leere Liste).
Das tatsächliche Laufergebnis sieht jedoch so aus:
>>> >["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
Ist das nicht seltsam? Warum wird der Standardwert „baz“ bei jedem Aufruf der Funktion foo() zur vorhandenen Liste hinzugefügt, anstatt eine neue leere Liste zu erstellen?
Die Antwort ist, dass die Einstellung des Standardwerts des optionalen Parameters in Python nur einmal ausgeführt wird, nämlich wenn die Funktion definiert wird. Daher wird der Balkenparameter nur dann auf den Standardwert (d. h. eine leere Liste) initialisiert, wenn die Funktion foo () definiert ist. Bei jedem weiteren Aufruf der Funktion foo () bleibt der Balkenparameter jedoch bestehen mit dem ursprünglichen Wert dieser Liste initialisiert.
Natürlich ist eine gängige Lösung:
>>> >... bar = []
... bar.append("baz")
... return bar
...
>>> >["baz"]
>>> foo()
["baz"]
>>> FAQ 2: Falsche Verwendung von Klassenvariablen
Schauen wir uns das folgende Beispiel an:
>>> Klasse A(Objekt):
... x = 1
..
>>> Klasse B(A):
... bestanden
...
>>> Klasse C(A):
.
...
>>> print A.x, B.x, C.x
1 1 1
Dieses Ergebnis ist normal.
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
Nun, das Ergebnis ist wie erwartet.
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
In der Python-Sprache basieren Klassenvariablen auf dem Wörterbuch It wird im Formular verarbeitet und folgt der Methodenauflösungsreihenfolge (Method Resolution Order, MRO). Da es im obigen Code kein x-Attribut in der Klasse C gibt, sucht der Interpreter daher nach seiner Basisklasse (obwohl Python Mehrfachvererbung unterstützt, ist in diesem Beispiel die Basisklasse von C nur A). Mit anderen Worten: C hat kein eigenes x-Attribut, das von A unabhängig ist und wirklich zu A gehört. Daher bezieht sich die Bezugnahme auf C.x tatsächlich auf A.x. Wenn die Beziehung hier nicht richtig gehandhabt wird, führt dies zu dem Problem im Beispiel.
Häufiger Fehler 3: Falsche Angabe der Parameter des Ausnahmeblocks (Ausnahmeblock)
Bitte schauen Sie sich den folgenden Code an:
>>> versuchen Sie:
... l = ["a", "b"]
... int(l[2])
... außer ValueError, IndexError: # Um beide Ausnahmen abzufangen, richtig?
... pass
...
Traceback (letzter Aufruf zuletzt):
Datei „
IndexError: Listenindex außerhalb des gültigen Bereichs
Das Problem bei diesem Code besteht darin, dass die Except-Anweisung die Angabe von Ausnahmen auf diese Weise nicht unterstützt. In Python 2.x müssen Sie die Variable e verwenden, um die Ausnahme an den optionalen zweiten Parameter zu binden, um die Ausnahme weiter anzuzeigen. Daher fängt die Except-Anweisung im obigen Code nicht die IndexError-Ausnahme ab, sondern bindet die auftretende Ausnahme an einen Parameter namens IndexError.
Um mehrere Ausnahmen in einer Ausnahmeanweisung korrekt abzufangen, sollten Sie den ersten Parameter als Tupel angeben und dann den Ausnahmetyp, den Sie abfangen möchten, in das Tupel schreiben. Um die Portabilität zu verbessern, verwenden Sie außerdem das Schlüsselwort as, das sowohl in Python 2 als auch in Python 3 unterstützt wird.
>>> try:
... l = ["a", "b"]
... int(l[2])
... außer (ValueError, IndexError) als e:
... pass
...
>>>
Häufiger Fehler 4: Missverständnis der Variablennamenanalyse in Python
Variablen in Python Namensauflösung folgt dem sogenannten LEGB-Prinzip, das heißt „L: lokaler Bereich; E: lokaler Bereich von def oder Lambda in der oberen Schichtstruktur; Eingebaut), in der Reihenfolge suchen. Sieht es nicht einfach aus? Allerdings gibt es tatsächlich einige Besonderheiten in der Wirkungsweise dieses Prinzips. Apropos, wir müssen die folgenden häufigen Python-Programmierfehler erwähnen. Bitte schauen Sie sich den Code unten an:
>>> x = 10
>>> ;> foo()
Traceback (letzter Aufruf zuletzt):
Datei „
UnboundLocalError: Lokale Variable 'x', auf die vor der Zuweisung verwiesen wurde
Was ist schief gelaufen?
Der obige Fehler tritt auf, weil wenn Sie einer Variablen in einem bestimmten Bereich einen Wert zuweisen, die Variable vom Python-Interpreter automatisch als lokale Variable des Bereichs betrachtet wird und alle Variablen der oberen Ebene ersetzt mit dem gleichen Namen.
Genau aus diesem Grund erscheint der Code, der gut angefangen hat, aber nach dem Hinzufügen einer Zuweisungsanweisung innerhalb einer Funktion tritt ein UnboundLocalError auf. Kein Wunder, dass es viele Leute überrascht.
Python-Programmierer geraten besonders häufig in diese Falle, wenn sie mit Listen arbeiten.
Bitte sehen Sie sich das folgende Codebeispiel an:
>>> lst = [1, 2, 3]
>>> . lst.append(5) # Kein Problem hier
...
>>> foo1()
>>> ]
>>> lst = [1, 2, 3]
>>>... lst = [5] # ... aber hier Das ist nicht richtig!
...
>>> foo2()
Traceback (letzter Aufruf zuletzt):
Datei „
Datei „
UnboundLocalError: Auf die lokale Variable 'lst' wurde vor der Zuweisung verwiesen
Häh? Warum läuft die Funktion foo1 normal, aber in foo2 tritt ein Fehler auf?
Die Antwort ist die gleiche wie im vorherigen Beispiel, aber etwas schwer fassbarer. Die Funktion foo1 weist der lst-Variablen keinen Wert zu, foo2 jedoch schon. Wir wissen, dass lst = [5] nur die Abkürzung von lst = lst [5] ist, woraus wir erkennen können, dass die Funktion foo2 versucht, lst einen Wert zuzuweisen (daher wird sie als Variable im lokalen Bereich von betrachtet). die Funktion durch den Python-Interpreter). Der Wert, den wir lst zuweisen möchten, basiert jedoch auf der lst-Variablen selbst (zu diesem Zeitpunkt wird sie auch als Variable im lokalen Bereich der Funktion betrachtet), was bedeutet, dass die Variable noch nicht definiert wurde. Da ist der Fehler aufgetreten.
Häufiger Fehler 5: Ändern der Liste beim Durchlaufen der Liste
Das Problem mit dem folgenden Code sollte sehr offensichtlich sein:
>>> odd = lambda x : bool(x % 2 )
>>> Zahlen = [n für n im Bereich(10)]
>>> für i im Bereich(len(Zahlen)):
... wenn ungerade ( zahlen[i]):
... del zahlen[i] # SCHLECHT: Element wird aus einer Liste gelöscht, während darüber iteriert wird
...
Traceback (letzter Aufruf zuletzt):
Datei „
IndexError: Listenindex außerhalb des gültigen Bereichs
Jede Erfahrung mit dem Entfernen von Elementen aus einer Liste oder einem Array während der Iteration Es handelt sich um Probleme, die erfahrenen Python-Entwicklern bekannt sind. Obwohl das obige Beispiel offensichtlich ist, ist es wahrscheinlich, dass erfahrene Entwickler versehentlich denselben Fehler machen, wenn sie komplexeren Code schreiben.
Glücklicherweise enthält die Python-Sprache viele elegante Programmierparadigmen, die bei richtiger Anwendung Ihren Code erheblich vereinfachen können. Ein weiterer Vorteil der Vereinfachung des Codes besteht darin, dass es weniger wahrscheinlich ist, dass beim Durchlaufen der Liste der Fehler beim Löschen von Elementen auftritt. Ein Programmierparadigma, das dies erreichen kann, ist das Listenverständnis. Darüber hinaus sind Listenverständnisse besonders nützlich, um dieses Problem zu vermeiden. Verwenden wir Listenverständnisse, um die Funktion des obigen Codes neu zu implementieren:
>>> odd = lambda x : bool(x % 2)
> >> Zahlen = [n für n im Bereich(10)]
>>> Zahlen[:] = [n für n in Zahlen, wenn nicht ungerade(n)] # Ahh, das Schöne daran alle
>>> Zahlen
[0, 2, 4, 6, 8]
Häufiger Fehler 6: Verstehe nicht, wie Python Variablen in Abschlüssen bindet
Bitte schauen Sie am folgenden Code:
>>>... return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
... print multiplier(2)
. ..
Sie denken vielleicht, dass das Ausgabeergebnis so aussehen sollte:
Das tatsächliche Ausgabeergebnis ist jedoch:
Shock it!
Dieses Ergebnis tritt hauptsächlich aufgrund des späten Bindungsmechanismus in Python auf, d. h. der Wert der Variablen im Abschluss wird nur abgefragt, wenn die interne Funktion aufgerufen wird. Daher wird im obigen Code jedes Mal, wenn die von create_multipliers() zurückgegebene Funktion aufgerufen wird, der Wert der Variablen i im nahegelegenen Bereich nachgeschlagen (und zu diesem Zeitpunkt ist die Schleife beendet, sodass die Variable i endgültig zugewiesen ist). Wert ist 4).
Um dieses häufige Python-Problem zu lösen, müssen Sie einige Hacks verwenden:
>>> * x für i in range(5)]
...
>>> für Multiplikator in create_multipliers():
... print multiplier(2)
.. .
0
2
4
6
8
Bitte beachten! Wir verwenden hier Standardparameter, um diese anonyme Lambda-Funktion zu implementieren. Manche finden es vielleicht elegant, manche halten es für clever und wieder andere spotten vielleicht. Wenn Sie jedoch ein Python-Programmierer sind, sollten Sie diese Lösung trotzdem kennen.
Häufiger Fehler 7: Zirkuläre Abhängigkeiten zwischen Modulen
Angenommen, Sie haben zwei Dateien, a.py und b.py, die aufeinander verweisen, wie unten gezeigt:
Code in a. py file:
import b
def f():
return b.x
print f()
b.py file Code:
import a
x = 1
def g():
print a.f()
Zuerst versuchen wir, das a.py-Modul zu importieren:
Der Code läuft einwandfrei. Vielleicht ist das unerwartet. Schließlich haben wir hier das Problem der Zirkelverweise, also muss es doch Probleme geben, oder?
Die Antwort ist, dass das bloße Vorhandensein eines Zirkelverweises an sich kein Problem darstellt. Wenn ein Modul bereits referenziert ist, kann Python verhindern, dass es erneut referenziert wird. Wenn jedoch jedes Modul zum falschen Zeitpunkt versucht, auf Funktionen oder Variablen zuzugreifen, die von anderen Modulen definiert wurden, geraten Sie wahrscheinlich in Schwierigkeiten.
Also zurück zu unserem Beispiel: Wenn wir das a.py-Modul importieren, wird es kein Problem geben, wenn es auf das b.py-Modul verweist, da das b.py-Modul nicht benötigt wird, wenn auf es verwiesen wird jede Variable oder Funktion, die im a.py-Modul definiert ist. Der einzige Verweis auf Modul a im b.py-Modul ist der Aufruf der foo()-Funktion von Modul a. Dieser Funktionsaufruf erfolgt jedoch in der g()-Funktion und die g()-Funktion wird weder im a.py- noch im b.py-Modul aufgerufen. Es wird also kein Problem auftreten.
Aber was ist, wenn wir versuchen, das b.py-Modul zu importieren (d. h. ohne vorher auf das a.py-Modul zu verweisen):
>>> import b
Traceback (aktuellster zuletzt aufrufen):
Datei „
Datei „b.py“, Zeile 1, in
importieren a
Datei „a.py“, Zeile 6, in
print f()
Datei „a.py“, Zeile 4, in f
return b.x
AttributeError : „Modul“-Objekt hat kein Attribut „x“
Ups. Die Situation ist nicht gut! Das Problem hierbei ist, dass beim Importieren von b.py versucht wird, auf das a.py-Modul zu verweisen, und das a.py-Modul dann die Funktion foo() aufruft, die dann versucht, auf die b.x-Variable zuzugreifen. Da die b.x-Variable jedoch zu diesem Zeitpunkt noch nicht definiert ist, ist die AttributeError-Ausnahme aufgetreten.
Es gibt eine sehr einfache Möglichkeit, dieses Problem zu lösen, nämlich einfach das b.py-Modul zu ändern und a.py nur innerhalb der g()-Funktion zu referenzieren:
x = 1
def g( ):
import a # Dies wird nur ausgewertet, wenn g() aufgerufen wird
print a.f()
Wenn wir nun das b.py-Modul importieren, gibt es keine Probleme:
>>> import b
>>> b.g()
1 # Wird zum ersten Mal gedruckt, da Modul 'a' am Ende 'print f()' aufruft
1 # Ein zweites Mal gedruckt, dies ist unser Aufruf von „g“
Häufiger Fehler 8: Die Modulbenennung steht in Konflikt mit dem Modulnamen der Python-Standardbibliothek
Einer der großen Vorteile der Python-Sprache ist dass es eine leistungsstarke Standardbibliothek mitbringt. Wenn Sie jedoch nicht genau aufpassen, können Sie Ihrem Modul aus diesem Grund denselben Namen wie Pythons eigenes Standardbibliotheksmodul geben (wenn Ihr Code beispielsweise ein Modul mit dem Namen email.py enthält, führt dies zu Konflikten mit dem gleichnamigen Modul in der Python-Standardbibliothek)
Dies wird wahrscheinlich schwierige Probleme für Sie verursachen. Wenn Modul A beispielsweise beim Importieren von Modul A versucht, auf Modul B in der Python-Standardbibliothek zu verweisen, Sie jedoch bereits über ein Modul B mit demselben Namen verfügen, verweist Modul A fälschlicherweise auf Modul B in Ihrem eigenen Code Modul B in der Python-Standardbibliothek. Dies ist auch die Ursache für einige schwerwiegende Fehler.
Daher sollten Python-Programmierer besonders darauf achten, nicht dieselben Namen wie Python-Standardbibliotheksmodule zu verwenden. Schließlich ist es viel einfacher, den Namen Ihres eigenen Moduls zu ändern, als einen PEP-Vorschlag vorzuschlagen, um den Namen eines Upstream-Moduls zu ändern und den Vorschlag zu akzeptieren.
Häufiger Fehler 9: Fehler beim Auflösen der Unterschiede zwischen Python 2 und Python 3
Angenommen, Sie haben den folgenden Code:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int(sys.argv[1]))
außer KeyError als e:
print('key error')
außer ValueError als e:
print('value error')
print(e)
bad()
Wenn es Python 2 ist, dann läuft der Code einwandfrei:
$ python foo.py 1
Schlüsselfehler
1
$ python foo.py 2
Wertfehler
2
Aber jetzt wechseln wir zu Python 3 und führen es erneut aus:
$ python3 foo.py 1
Schlüsselfehler
Traceback (letzter Aufruf zuletzt):
Datei „foo.py“, Zeile 19, in
bad()
Datei „foo.py“, Zeile 17, in bad
print(e)
UnboundLocalError: lokale Variable 'e' vor der Zuweisung referenziert
Was zum Teufel ist los? Das „Problem“ hierbei ist, dass in Python 3 auf Ausnahmeobjekte außerhalb des Gültigkeitsbereichs des Except-Blocks nicht zugegriffen werden kann. (Der Grund für dieses Design besteht darin, dass die Referenzschleife andernfalls im Stapelrahmen verbleibt, bis der Garbage Collector ausgeführt wird und die Referenz aus dem Speicher gelöscht wird.)
Eine Möglichkeit, dieses Problem zu vermeiden Die Methode besteht darin, sie beizubehalten Ein Verweis auf das Ausnahmeobjekt außerhalb des Gültigkeitsbereichs des Ausnahmecodeblocks, sodass auf das Ausnahmeobjekt zugegriffen werden kann. Der folgende Code verwendet diese Methode, sodass die Ausgabeergebnisse in Python 2 und Python 3 konsistent sind:
import sys
def bar(i):
if i == 1:
raise KeyError(1 )
if i == 2:
raise ValueError(2)
def good():
Ausnahme = Keine
try:
bar(int( sys.argv[1] ))
außer KeyError als e:
Ausnahme = e
print('Schlüsselfehler')
außer ValueError als e:
Ausnahme = e
print ('Wertfehler')
print(Exception)
good()
Führen Sie den Code unter Python 3 aus:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
Wertfehler
2
Großartig!
Häufiger Fehler 10: Falsche Verwendung der del-Methode
Angenommen, Sie schreiben den folgenden Code in die mod.py-Datei:
import foo
class Bar(object):
. .
def __del__(self):
foo.cleanup(self.myhandle)
Danach führen Sie die folgenden Vorgänge in der another_mod.py-Datei aus:
import mod
mybar = mod.Bar()
Wenn Sie das Modul another_mod.py ausführen, tritt eine AttributeError-Ausnahme auf.
Warum? Denn wenn der Interpreter endet, werden die globalen Variablen des Moduls auf None gesetzt. Daher wurde foo im obigen Beispiel auf None gesetzt, bevor die Methode __del__ aufgerufen wird.
Eine Möglichkeit, dieses etwas knifflige Python-Programmierproblem zu lösen, ist die Verwendung der Methode atexit.register(). In diesem Fall wird der von Ihnen angegebene Handler ausgeführt, bevor der Interpreter geschlossen wird, wenn die Ausführung Ihres Programms abgeschlossen ist (d. h. wenn das Programm normal beendet wird).
Nach Anwendung der oben genannten Methode könnte die geänderte mod.py-Datei wie folgt aussehen:
import foo
import atexit
def cleanup(handle):
foo .cleanup(handle )
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Diese Implementierung unterstützt sauberer Aufruf aller notwendigen Bereinigungsfunktionen bei normaler Programmbeendigung. Offensichtlich entscheidet im obigen Beispiel die Funktion foo.cleanup, wie mit dem an self.myhandle gebundenen Objekt umgegangen wird.
Übersicht
Python ist eine leistungsstarke und flexible Programmiersprache, die viele Programmiermechanismen und Paradigmen bereitstellt, die die Arbeitseffizienz erheblich verbessern können. Aber wie bei jedem Softwaretool oder jeder Sprache gilt: Wenn Sie nur ein begrenztes Verständnis oder Verständnis für die Fähigkeiten der Sprache haben, können Sie manchmal eher behindert als davon profitiert werden. Ein Sprichwort sagt: „Der Glaube, dass Sie genug wissen, kann Sie oder andere gefährden.
Machen Sie sich kontinuierlich mit den Feinheiten der Python-Sprache vertraut, insbesondere mit den in diesem Artikel genannten Top 10. Häufige Fehler werden Ihnen bei der Verwendung helfen Lernen Sie Ihre Sprache effektiv und vermeiden Sie einige der häufigsten Fehler