Über Python
Python ist eine interpretierte, objektorientierte Programmiersprache auf hoher Ebene mit dynamischer Semantik. Integrierte Datenstrukturen auf hoher Ebene in Kombination mit dynamischer Typisierung und dynamischer Bindung machen es für BRPS (schnelle Entwicklung von Anwendungstools) sowie für die Verwendung als Skript- und Verbindungssprache zum Verbinden vorhandener Komponenten oder Dienste sehr attraktiv. Python unterstützt Module und Pakete und fördert so die Programmmodularität und die Wiederverwendung von Code.
Über diesen Artikel
Die Einfachheit und Leichtigkeit, diese Sprache zu beherrschen, kann für Entwickler verwirrend sein (insbesondere für diejenigen, die gerade erst anfangen, Python zu lernen), sodass Sie einige wichtige Feinheiten aus den Augen verlieren und die Leistungsfähigkeit der Vielzahl möglicher Lösungen mit Python unterschätzen können.
In diesem Sinne werden in diesem Artikel die „Top 10“ subtiler, schwer zu findender Fehler vorgestellt, die selbst fortgeschrittene Python-Entwickler machen können.
Fehler Nr. 1: Missbrauch von Ausdrücken als Standardwerte für Funktionsargumente
Mit Python können Sie angeben, dass eine Funktion optionale Argumente haben kann, indem Sie einen Standardwert für diese festlegen. Dies ist natürlich ein sehr praktisches Merkmal der Sprache, kann jedoch zu unangenehmen Konsequenzen führen, wenn der Typ dieses Werts veränderlich ist. Betrachten Sie beispielsweise die folgende Funktionsdefinition:
>>> def foo(bar=[]):
Ein häufiger Fehler in diesem Fall ist die Annahme, dass der Wert eines optionalen Arguments bei jedem Aufruf einer Funktion ohne Wert für dieses Argument auf den Standardwert gesetzt wird. Im obigen Code können wir beispielsweise davon ausgehen, dass durch wiederholtes Aufrufen der Funktion foo () (dh ohne Angabe eines Werts für das Balkenargument) immer "baz" zurückgegeben wird, da davon ausgegangen wird, dass jedes Mal, wenn foo () aufgerufen wird (ohne) unter Angabe der Argumentleiste) wird die Leiste auf [] gesetzt (d. h. eine neue leere Liste).
Aber mal sehen, was tatsächlich passieren wird:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
Huh? Warum fügt die Funktion der vorhandenen Liste bei jedem Aufruf von foo () weiterhin den Standardwert "baz" hinzu, anstatt jedes Mal eine neue Liste zu erstellen?
Die Antwort auf diese Frage wird ein tieferes Verständnis dafür sein, was mit Python „unter der Haube“ vor sich geht. Nämlich: Der Standardwert für die Funktion wird während der Definition der Funktion nur einmal initialisiert. Daher wird das Balkenargument standardmäßig nur dann initialisiert (d. H. Eine leere Liste), wenn foo () zum ersten Mal definiert wird, aber nachfolgende Aufrufe von foo () (d. H. Ohne Angabe des Balkenarguments) verwenden weiterhin dieselbe Liste wie zuvor Erstellt für die Argumentleiste zum Zeitpunkt der ersten Funktionsdefinition.
Als Referenz ist die folgende Definition eine häufige „Problemumgehung“ für diesen Fehler:
>>> def foo(bar=None): ... if bar is None:
Fehler Nr. 2: Missbrauch von Klassenvariablen
Betrachten Sie das folgende Beispiel:
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print Ax, Bx, Cx 1 1 1
Alles scheint in Ordnung zu sein.
>>> Bx = 2 >>> print Ax, Bx, Cx 1 2 1
Ja, alles war wie erwartet.
>>> Ax = 3 >>> print Ax, Bx, Cx 3 2 3
Was zur Hölle ?! Wir haben gerade Axe geändert. Warum hat sich Cx auch geändert?
In Python werden Klassenvariablen wie Wörterbücher behandelt und folgen der sogenannten Method Resolution Order (MRO). Da das Attribut x im obigen Code nicht in Klasse C gefunden wird, wird es daher in seinen Basisklassen gefunden (im obigen Beispiel nur A, obwohl Python Mehrfachvererbung unterstützt). Mit anderen Worten, C hat keine eigene Eigenschaft x unabhängig von A. Somit sind Verweise auf Cx tatsächlich Verweise auf Ax. Dies führt zu Problemen, wenn diese Fälle nicht richtig behandelt werden. Achten Sie beim Erlernen von Python besonders auf Klassenattribute und arbeiten Sie mit ihnen.
Fehler Nr. 3: Falsche Parameter für den Ausnahmeblock
Angenommen, Sie haben den folgenden Code:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError:
Das Problem hierbei ist, dass der Ausnahmeausdruck die auf diese Weise angegebene Liste von Ausnahmen nicht akzeptiert. Vielmehr wird in Python 2.x der Ausdruck "außer Ausnahme, e" verwendet, um die Ausnahme an einen optionalen zweiten gegebenen zweiten Parameter (in diesem Fall e) zu binden, um sie für die weitere Überprüfung verfügbar zu machen. Infolgedessen wird im obigen Code eine IndexError-Ausnahme nicht von der Ausnahme-Anweisung abgefangen. Stattdessen endet die Ausnahme mit der Bindung an einen Parameter namens IndexError.
Der richtige Weg, um mehrere Ausnahmen mit dem Ausnahmeausdruck abzufangen, besteht darin, den ersten Parameter als Tupel anzugeben, das alle Ausnahmen enthält, die Sie abfangen möchten. Verwenden Sie für maximale Kompatibilität auch das Schlüsselwort as, da diese Syntax sowohl in Python 2 als auch in Python 3 unterstützt wird:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
Fehler Nr. 4: Missverständnis der Python-Bereichsregeln
Der Gültigkeitsbereich in Python basiert auf der sogenannten LEGB-Regel, bei der es sich um eine Abkürzung für Local (Namen, die in einer Funktion in irgendeiner Weise zugewiesen wurden (def oder lambda) und in dieser Funktion nicht als global deklariert sind), Enclosing (Name im lokalen Gültigkeitsbereich statisch einschließender Funktionen () handelt. def oder lambda), von intern nach extern), Global (Namen, die auf der obersten Ebene der Moduldatei zugewiesen wurden, oder durch Ausführen der globalen Anweisungen in def in der Datei), Integriert (Namen, die zuvor im Modul für integrierte Namen zugewiesen wurden: offen, Bereich, SyntaxError, ...). Es scheint einfach genug, oder? Nun, tatsächlich gibt es einige Feinheiten, wie dies in Python funktioniert, was uns zu dem allgemein komplexeren Python-Programmierproblem unten führt. Betrachten Sie das folgende Beispiel:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
Was ist das Problem?
Der obige Fehler tritt auf, weil Python beim Zuweisen einer Variablen im Bereich diese automatisch als lokal für diesen Bereich betrachtet und jede Variable mit demselben Namen in einem übergeordneten Bereich ausblendet.
Daher sind viele überrascht, wenn sie UnboundLocalError in zuvor ausgeführtem Code erhalten, wenn dieser durch Hinzufügen eines Zuweisungsoperators irgendwo im Funktionskörper geändert wird.
Diese Funktion ist für Entwickler bei der Verwendung von Listen besonders verwirrend. Betrachten Sie das folgende Beispiel:
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5)
Huh? Warum stürzt foo2 ab, während foo1 einwandfrei funktioniert?
Die Antwort ist die gleiche wie im vorherigen Beispiel, aber nach allgemeiner Meinung ist die Situation hier subtiler. foo1 wendet den Zuweisungsoperator nicht auf lst an, foo2 nicht. Wenn man bedenkt, dass lst + = [5] eigentlich nur eine Abkürzung für lst = lst + [5] ist, sehen wir, dass wir versuchen, den Wert lst zuzuweisen (Python geht also davon aus, dass er im lokalen Bereich liegt). Der Wert, den wir lst zuweisen möchten, basiert jedoch auf lst selbst (es wird nun wieder angenommen, dass er sich im lokalen Bereich befindet), der noch nicht bestimmt wurde. Und wir bekommen einen Fehler.
Fehler Nr. 5: Ändern einer Liste während der Iteration darüber
Das Problem im folgenden Code sollte ziemlich offensichtlich sein:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i]
Das Entfernen eines Elements aus einer Liste oder einem Array während der Iteration ist ein Python-Problem, das jedem erfahrenen Softwareentwickler bekannt ist. Obwohl das obige Beispiel ziemlich offensichtlich sein mag, können selbst erfahrene Entwickler diesen Rechen mit viel komplexerem Code beginnen.
Glücklicherweise enthält Python eine Reihe eleganter Programmierparadigmen, die bei richtiger Verwendung zu einer erheblichen Vereinfachung und Optimierung des Codes führen können. Eine weitere erfreuliche Folge davon ist, dass in einfacherem Code die Wahrscheinlichkeit, in den Fehler zu geraten, ein Listenelement während der Iteration versehentlich zu löschen, viel geringer ist. Ein solches Paradigma sind Listengeneratoren. Darüber hinaus ist das Verständnis der Funktionsweise von Listengeneratoren besonders hilfreich, um dieses spezielle Problem zu vermeiden, wie in dieser alternativen Implementierung des obigen Codes gezeigt, die einwandfrei funktioniert:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)]
Fehler Nr. 6: Missverständnis, wie Python Variablen in Abschlüssen bindet
Betrachten Sie das folgende Beispiel:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
Sie können die folgende Ausgabe erwarten:
0 2 4 6 8
Aber eigentlich bekommst du das:
8 8 8 8 8
Überraschung!
Dies ist auf die späte Bindung in Python zurückzuführen. Dies bedeutet, dass die Werte der in Closures verwendeten Variablen während des Aufrufs der internen Funktion nachgeschlagen werden. Daher wird im obigen Code bei jedem Aufruf einer der zurückgegebenen Funktionen der Wert i während des Aufrufs im umgebenden Bereich gesucht (und zu diesem Zeitpunkt war der Zyklus bereits abgeschlossen, sodass mir bereits das Endergebnis zugewiesen wurde - Wert 4). .
Die Lösung für dieses häufig auftretende Python-Problem wäre:
>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8
Voila! Wir verwenden hier die Standardargumente, um anonyme Funktionen zu generieren, um das gewünschte Verhalten zu erzielen. Einige würden diese Lösung als elegant bezeichnen. Einige sind
dünn. Einige Leute hassen diese Dinge. Aber wenn Sie ein Python-Entwickler sind, ist es trotzdem wichtig zu verstehen.
Fehler Nr. 7: Erstellen zyklischer Modulabhängigkeiten
Angenommen, Sie haben zwei Dateien, a.py und b.py, von denen jede die andere wie folgt importiert:
In a.py:
import b def f(): return bx print f()
In b.py:
import a x = 1 def g(): print af()
Versuchen Sie zunächst, a.py zu importieren:
>>> import a 1
Es hat gut funktioniert. Das kann Sie überraschen. Schließlich importieren sich Module zyklisch und das sollte wahrscheinlich ein Problem sein, oder?
Die Antwort ist, dass der einfache zyklische Import von Modulen an sich kein Problem in Python ist. Wenn das Modul bereits importiert wurde, ist Python intelligent genug, um nicht zu versuchen, es erneut zu importieren. Abhängig von dem Punkt, an dem jedes Modul versucht, auf Funktionen oder Variablen zuzugreifen, die in einem anderen Modul definiert sind, können jedoch tatsächlich Probleme auftreten.
Zurück zu unserem Beispiel: Beim Importieren von a.py gab es keine Probleme beim Importieren von b.py, da b.py nicht erfordert, dass a.py während des Imports definiert wird. Der einzige Verweis in b.py auf a ist ein Aufruf von af (). Aber dieser Aufruf in g () und nichts in a.py oder b.py ruft g () nicht auf. Also funktioniert alles gut.
Aber was passiert, wenn wir versuchen, b.py zu importieren (ohne zuerst a.py zu importieren):
>>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return bx AttributeError: 'module' object has no attribute 'x'
Oh, oh. Das ist nicht gut! Das Problem hierbei ist, dass während des Importvorgangs von b.py versucht wird, a.py zu importieren, was wiederum f () aufruft, das versucht, auf bx zuzugreifen. Bx wurde jedoch noch nicht definiert. Daher die AttributeError-Ausnahme.
Mindestens eine Lösung für dieses Problem ist ziemlich trivial. Ändern Sie einfach b.py, um a.py in g () zu importieren:
x = 1 def g(): import a
Wenn wir es jetzt importieren, ist alles in Ordnung:
>>> import b >>> bg() 1
Fehler Nr. 8: Überschneiden von Namen mit Modulnamen in der Python-Standardbibliothek
Einer der Reize von Python sind die vielen Module, die sofort einsatzbereit sind. Wenn Sie dies jedoch nicht bewusst befolgen, stellen Sie möglicherweise fest, dass der Name Ihres Moduls mit dem Namen des Moduls in der mit Python gelieferten Standardbibliothek identisch ist (z. B. enthält Ihr Code möglicherweise ein Modul mit dem Namen email.py, die mit dem gleichnamigen Standardbibliotheksmodul in Konflikt steht).
Dies kann zu ernsthaften Problemen führen. Wenn beispielsweise eines der Module versucht, die Version des Moduls aus der Python-Standardbibliothek zu importieren, und Sie im Projekt ein Modul mit demselben Namen haben, das versehentlich anstelle des Moduls aus der Standardbibliothek importiert wird.
Daher sollte darauf geachtet werden, nicht dieselben Namen wie in den Modulen der Python-Standardbibliothek zu verwenden. Es ist viel einfacher, den Namen des Moduls in Ihrem Projekt zu ändern, als eine Anfrage zu senden, um den Namen des Moduls in der Standardbibliothek zu ändern und die Genehmigung dafür zu erhalten.
Fehler Nr. 9: Die Unterschiede zwischen Python 2 und Python 3 wurden nicht berücksichtigt
Betrachten Sie die folgende foo.py-Datei:
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])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()
Unter Python 2 funktioniert es einwandfrei:
$ python foo.py 1 key error 1 $ python foo.py 2 value error 2
Aber jetzt wollen wir sehen, wie es in Python 3 funktioniert:
$ python3 foo.py 1 key error Traceback (most recent call last): File "foo.py", line 19, in <module> bad() File "foo.py", line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
Was ist gerade hier passiert? Das "Problem" ist, dass in Python 3 ein Objekt in einem Ausnahmeblock außerhalb nicht verfügbar ist. (Der Grund dafür ist, dass ansonsten die Objekte in diesem Block im Speicher gespeichert werden, bis der Garbage Collector startet und Verweise auf sie von dort entfernt.)
Eine Möglichkeit, dieses Problem zu vermeiden, besteht darin, den Verweis auf das Ausnahmeblockobjekt außerhalb dieses Blocks zu belassen, damit es verfügbar bleibt. Hier ist die Version des vorherigen Beispiels, in der diese Technik verwendet wird, um Code zu erhalten, der sowohl für Python 2 als auch für Python 3 geeignet ist:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()
Führen Sie es in Python 3 aus:
$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2
Hurra!
Fehler Nr. 10: Unsachgemäße Verwendung der Methode __del__
Angenommen, Sie haben eine mod.py-Datei wie diese:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
Und Sie versuchen dies von einer anderen another_mod.py aus zu tun:
import mod mybar = mod.Bar()
Und bekomme einen schrecklichen AttributeError.
Warum? Denn wie
hier berichtet, haben alle globalen Variablen des Moduls beim Herunterfahren des Interpreters den Wert None. Infolgedessen wurde im obigen Beispiel beim Aufrufen von __del__ der Name foo bereits auf None gesetzt.
Die Lösung für diese "Aufgabe mit einem Sternchen" besteht in der Verwendung von atexit.register (). Wenn Ihr Programm die Ausführung abgeschlossen hat (dh wenn es normal beendet wird), werden Ihre Handles gelöscht, bevor der Interpreter seine Arbeit beendet.
In diesem Sinne könnte das Update für den obigen mod.py-Code ungefähr so aussehen:
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
Eine solche Implementierung bietet eine einfache und zuverlässige Möglichkeit, die erforderliche Bereinigung nach einer normalen Programmbeendigung aufzurufen. Natürlich bleibt die Entscheidung, wie mit dem Objekt umgegangen werden soll, das mit dem Namen self.myhandle verknüpft ist, foo.cleanup überlassen, aber ich denke, Sie verstehen die Idee.
Fazit
Python ist eine leistungsstarke und flexible Sprache mit vielen Mechanismen und Paradigmen, die die Leistung erheblich verbessern können. Wie bei jedem Software-Tool oder jeder Software können jedoch bei einem eingeschränkten Verständnis oder einer eingeschränkten Bewertung ihrer Funktionen unvorhergesehene Probleme während der Entwicklung auftreten.
Eine Einführung in die in diesem Artikel behandelten Python-Nuancen hilft Ihnen dabei, die Verwendung der Sprache zu optimieren und einige häufige Fehler zu vermeiden.