
Hallo habrozhiteli! Das Erlernen aller Möglichkeiten von Python ist eine schwierige Aufgabe, und mit diesem Buch können Sie sich auf praktische Fähigkeiten konzentrieren, die wirklich wichtig sind. Graben Sie verstecktes Gold in der Python-Standardbibliothek aus und schreiben Sie noch heute sauberen Code.
Wenn Sie Erfahrung mit älteren Versionen von Python haben, können Sie die Arbeit mit modernen Vorlagen und Funktionen beschleunigen, die in Python 3 eingeführt wurden.
Wenn Sie mit anderen Programmiersprachen gearbeitet haben und zu Python wechseln möchten, finden Sie praktische Tipps, die Sie benötigen, um ein effektiver Pythonist zu werden.
Wenn Sie lernen möchten, wie man sauberen Code schreibt, finden Sie hier die interessantesten Beispiele und wenig bekannten Tricks.
Auszug "Der verrückteste Wörterbuchausdruck im Westen"
Manchmal stößt man auf ein winziges Beispiel für Code mit wirklich unerwarteter Tiefe - eine einzelne Codezeile, die Ihnen viel beibringen kann, wenn Sie sorgfältig darüber nachdenken. Ein solcher Code ist wie ein Koan im Zen-Buddhismus: eine Frage oder Aussage, die in der Zen-Praxis verwendet wird, um Zweifel zu wecken und die Leistung der Schüler zu testen.
Der winzige Code, den wir in diesem Abschnitt behandeln, ist ein solches Beispiel. Auf den ersten Blick mag es wie ein unkomplizierter Wortschatz aussehen, aber bei näherer Betrachtung werden Sie mit dem Python-Interpreter auf eine psychedelische Kreuzfahrt gebracht, die den Geist erweitert.
Von diesem Einzeiler bekomme ich eine solche Begeisterung, dass ich ihn einmal auf meinem Python-Konferenzteilnehmerausweis gedruckt habe, um mich zu unterhalten. Dies führte zu einigen konstruktiven Dialogen mit Mitgliedern meiner Python-E-Mail-Liste.
Also, ohne weiteres, hier ist dieser Code. Machen Sie eine Pause, um über den folgenden Wortschatzausdruck nachzudenken und darüber, wozu er führen sollte:
>>> {True: '', 1: '', 1.0: ''}
Ich werde hier warten ...
OK, fertig?
Das folgende Ergebnis erhalten wir, wenn wir den obigen Wörterbuchausdruck in einer Python-Interpretersitzung auswerten:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
Ich gebe zu, als ich dieses Ergebnis zum ersten Mal sah, war ich sehr verblüfft. Aber alles passt zusammen, wenn Sie gemächlich Schritt für Schritt untersuchen, was hier passiert. Lassen Sie uns darüber nachdenken, warum wir dieses, ich muss sagen, nicht sehr intuitive Ergebnis erhalten.
Wenn Python unseren Wörterbuchausdruck verarbeitet, erstellt es zuerst ein neues leeres Wörterbuchobjekt und weist ihm dann Schlüssel und Werte in der Reihenfolge zu, in der sie an den Wörterbuchausdruck übergeben werden.
Wenn wir es dann in Teile zerlegen, entspricht unser Wörterbuchausdruck der folgenden Folge von Anweisungen, die der Reihe nach ausgeführt werden:
>>> xs = dict() >>> xs[True] = '' >>> xs[1] = '' >>> xs[1.0] = ''
Seltsamerweise betrachtet Python alle in diesem Wörterbuchbeispiel verwendeten Schlüssel als gleichwertig:
>>> True == 1 == 1.0 True
Okay, aber warte eine Minute. Ich bin sicher, Sie können intuitiv zugeben, dass 1.0 == 1 ist, aber warum wird True auch als gleichwertig mit 1 angesehen? Als ich diesen Wörterbuchausdruck zum ersten Mal sah, war ich wirklich verwirrt.
Beim Stöbern in der Python-Dokumentation stellte ich fest, dass Python den Bool-Typ als Unterklasse des Int-Typs behandelt. Dies ist in Python 2 und Python 3 der Fall:
Der boolesche Typ ist ein Subtyp des ganzzahligen Typs, und die booleschen Werte verhalten sich in fast allen Kontexten wie die Werte 0 und 1, mit der Ausnahme, dass bei der Konvertierung in einen Zeichenfolgentyp die Zeichenfolgenwerte 'False' bzw. 'True' zurückgegeben werden '.
Dies bedeutet natürlich, dass in Python Boolesche Werte technisch als Listen- oder Tupelindizes verwendet werden können:
>>> ['', ''][True] ''
Aber Sie sollten diese Art von logischer Variable wahrscheinlich nicht im Namen der Klarheit (und der psychischen Gesundheit Ihrer Kollegen) verwenden.
So oder so, zurück zu unserem Wörterbuchausdruck.
In der Python-Sprache repräsentieren alle diese Werte - True, 1 und 1.0 - denselben Wörterbuchschlüssel. Wenn der Interpreter einen Wörterbuchausdruck auswertet, überschreibt er wiederholt den Wert des True-Schlüssels. Dies erklärt, warum das resultierende Wörterbuch ganz am Ende nur einen Schlüssel enthält.
Bevor wir weiter gehen, werfen Sie einen weiteren Blick auf den ursprünglichen Wörterbuchausdruck:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
Warum werden wir hier immer noch als Schlüssel wahr? Sollte sich der Schlüssel nicht aufgrund wiederholter Zuweisungen am Ende auch auf 1.0 ändern?
Nach einigen Recherchen im Quellcode des Python-Interpreters stellte ich fest, dass Python-Wörterbücher selbst dieses Schlüsselobjekt nicht aktualisieren, wenn einem Schlüsselobjekt ein neuer Wert zugeordnet wird:
>>> ys = {1.0: ''} >>> ys[True] = '' >>> ys {1.0: ''}
Dies ist natürlich als Leistungsoptimierung sinnvoll: Wenn die Schlüssel als identisch angesehen werden, warum dann Zeit damit verschwenden, das Original zu aktualisieren?
Im letzten Beispiel haben Sie gesehen, dass das ursprüngliche True-Objekt als Schlüssel niemals ersetzt wird. Aus diesem Grund druckt die Zeichenfolgendarstellung des Wörterbuchs den Schlüssel weiterhin als True (anstelle von 1 oder 1.0).
Nach dem, was wir jetzt wissen, werden die Werte im resultierenden Wörterbuch anscheinend nur neu geschrieben, weil der Vergleich sie immer als gleichwertig anzeigt. Es stellt sich jedoch heraus, dass dieser Effekt auch keine Folge des Äquivalenztests nach der Methode __eq__ ist.
Python-Wörterbücher basieren auf einer Hash-Tabellendatenstruktur. Als ich diesen erstaunlichen Wörterbuchausdruck zum ersten Mal sah, war mein erster Gedanke, dass ein solches Verhalten irgendwie mit Hash-Konflikten zusammenhängt.
Tatsache ist, dass die Hash-Tabelle in der internen Darstellung die darin verfügbaren Schlüssel in verschiedenen "Körben" entsprechend dem Hash-Wert jedes Schlüssels speichert. Der Hash-Wert wird vom Schlüssel als numerischer Wert fester Länge abgeleitet, der den Schlüssel eindeutig identifiziert.
Diese Tatsache ermöglicht es Ihnen, schnelle Suchvorgänge durchzuführen. Das Auffinden des Schlüssel-Hash-Werts in der Nachschlagetabelle ist viel schneller als das Vergleichen des gesamten Schlüsselobjekts mit allen anderen Schlüsseln und das Durchführen einer Äquivalenzprüfung.
Methoden zur Berechnung von Hashwerten sind jedoch im Allgemeinen nicht ideal. Und letztendlich haben zwei oder mehr Schlüssel, die tatsächlich unterschiedlich sind, denselben abgeleiteten Hashwert und landen im selben Suchtabellenkorb.
Wenn zwei Schlüssel denselben Hashwert haben, wird diese Situation als Hashkonflikt bezeichnet und ist ein Sonderfall, mit dem Algorithmen zum Einfügen und Suchen von Elementen in eine Hash-Tabelle behandelt werden sollten.
Basierend auf dieser Einschätzung ist es sehr wahrscheinlich, dass das Hashing in irgendeiner Weise mit dem unerwarteten Ergebnis zusammenhängt, das wir aus unserem Wörterbuchausdruck erhalten haben. Lassen Sie uns daher herausfinden, ob die Schlüssel-Hash-Werte auch hier eine bestimmte Rolle spielen.
Ich definiere die folgende Klasse als kleines Detektivwerkzeug:
class AlwaysEquals: def __eq__(self, other): return True def __hash__(self): return id(self)
Diese Klasse zeichnet sich durch zwei Aspekte aus.
Erstens, da die __eq__ Dunder-Methode immer True zurückgibt, geben alle Instanzen dieser Klasse vor, einem Objekt gleichwertig zu sein:
>>> AlwaysEquals() == AlwaysEquals() True >>> AlwaysEquals() == 42 True >>> AlwaysEquals() == '?' True
Und zweitens gibt jede Instanz von AlwaysEquals auch einen eindeutigen Hashwert zurück, der von der integrierten Funktion id () generiert wird:
>>> objects = [AlwaysEquals(), AlwaysEquals(), AlwaysEquals()] >>> [hash(obj) for obj in objects] [4574298968, 4574287912, 4574287072]
In Python gibt die Funktion id () die Adresse eines Objekts im RAM zurück, die garantiert eindeutig ist.
Mit dieser Klasse können Sie jetzt Objekte erstellen, die vorgeben, mit jedem anderen Objekt gleichwertig zu sein, denen jedoch gleichzeitig ein eindeutiger Hashwert zugeordnet ist. Auf diese Weise können Sie überprüfen, ob die Schlüssel des Wörterbuchs neu geschrieben wurden, und sich dabei nur auf das Ergebnis ihres Vergleichs der Äquivalenz verlassen.
Und wie Sie sehen, stimmen die Schlüssel im folgenden Beispiel nicht überein, obwohl der Vergleich sie immer als gleichwertig anzeigt:
>>> {AlwaysEquals(): '', AlwaysEquals(): ''} { <AlwaysEquals object at 0x110a3c588>: '', <AlwaysEquals object at 0x110a3cf98>: '' }
Wir können diese Idee auch von der anderen Seite betrachten und prüfen, ob die Rückgabe des gleichen Hashwerts ein ausreichender Grund ist, um das Umschreiben der Schlüssel zu erzwingen:
class SameHash: def __hash__(self): return 1
Wenn Sie Instanzen der SameHash-Klasse vergleichen, werden sie als nicht gleichwertig angezeigt, aber alle haben den gleichen Hashwert von 1:
>>> a = SameHash() >>> b = SameHash() >>> a == b False >>> hash(a), hash(b) (1, 1)
Mal sehen, wie Python-Wörterbücher reagieren, wenn wir versuchen, SameHash-Klasseninstanzen als Wörterbuchschlüssel zu verwenden:
>>> {a: 'a', b: 'b'} { <SameHash instance at 0x7f7159020cb0>: 'a', <SameHash instance at 0x7f7159020cf8>: 'b' }
Wie dieses Beispiel zeigt, wird der Effekt des „Überschreibens von Schlüsseln“ nicht nur durch Hashwertkonflikte verursacht.
Wörterbücher führen eine Äquivalenzprüfung durch und vergleichen den Hashwert, um festzustellen, ob die beiden Schlüssel identisch sind. Versuchen wir, die Ergebnisse unserer Studie zusammenzufassen.
Der Wörterbuchausdruck {Wahr: 'Ja', 1: 'Nein', 1.0: 'Vielleicht'} wird als {Wahr: 'Möglich'} berechnet, da beim Vergleich aller Schlüssel dieses Beispiels, Wahr, 1 und 1.0, diese angezeigt werden als äquivalent zueinander, und sie haben alle den gleichen Hash-Wert:
>>> True == 1 == 1.0 True >>> (hash(True), hash(1), hash(1.0)) (1, 1, 1)
Vielleicht ist es jetzt nicht so überraschend, dass wir gerade ein Ergebnis wie den Endzustand des Wörterbuchs erhalten haben:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
Hier haben wir viele Themen behandelt, und dieser spezielle Python-Trick passt möglicherweise zunächst nicht in den Kopf - deshalb habe ich ihn ganz am Anfang des Abschnitts mit einem Zen-Koan verglichen.
Wenn Sie Schwierigkeiten haben zu verstehen, was in diesem Abschnitt vor sich geht, versuchen Sie, nacheinander mit allen Codebeispielen in einer Python-Interpretersitzung zu experimentieren. Sie werden belohnt, indem Sie Ihr Wissen über die internen Mechanismen der Python-Sprache erweitern.
»Weitere Informationen zum Buch finden Sie auf
der Website des Herausgebers»
Inhalt»
Auszug20% Rabatt-Gutschein für Straßenhändler -
Python