Kapitel 4. Einführung in die Python-Objekttypen

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Mit diesem Kapitel beginnt unsere Tour durch die Sprache Python. In Python machen wir Dinge mit Dingen.1 "Dinge" haben die Form von Operationen wie Addition und Verkettung, und "Zeug" bezieht sich auf die Objekte, mit denen wir diese Operationen durchführen. In diesem Teil des Buches konzentrieren wir uns auf dieses Material und die Dinge, die unsere Programme damit tun können.

Etwas formeller ausgedrückt, nehmen Daten in Python die Form von Objekten an - entwederintegrierte Objekte, die Python bereitstellt, oder Objekte, die wir mit Python-Klassen oder externen Sprachwerkzeugen wie C-Erweiterungsbibliotheken erstellen. Obwohl wir diese Definition später noch präzisieren werden, sind Objekte im Grunde nur Speicherstücke mit Werten und zugehörigen Operationen. Wie wir noch sehen werden, ist alles in einem Python-Skript ein Objekt. Sogar einfache Zahlen sind Objekte, mit Werten (z. B. 99) und unterstützten Operationen (Addition, Subtraktion usw.).

Da Objekte auch der grundlegendste Begriff in der Python-Programmierung sind, beginnen wir dieses Kapitel mit einem Überblick über die in Python eingebauten Objekttypen. Spätere Kapitel bieten einen zweiten Durchgang, in dem wir uns mit Details beschäftigen, die wir in diesem Überblick übersehen haben. Unser Ziel ist es, einen kurzen Überblick über die Grundlagen zu geben.

Die Python-Konzepthierarchie

Bevor wir uns dem Code widmen, wollen wir uns zunächst ein klares Bild davon machen, wie dieses Kapitel in das Gesamtbild von Python passt. Aus einer konkreteren Perspektive lassen sich Python-Programme wie folgt in Module, Anweisungen, Ausdrücke und Objekte zerlegen:

  1. Programme sind aus Modulen zusammengesetzt.

  2. Module enthalten Anweisungen.

  3. Anweisungen enthalten Ausdrücke.

  4. Ausdrücke erstellen und verarbeiten Objekte.

Die Diskussion der Module in Kapitel 3hat die höchste Ebene dieser Hierarchie eingeführt. Die Kapitel dieses Teils beginnen auf der untersten Ebene - sie befassen sich sowohl mit den eingebauten Objekten als auch mit den Ausdrücken, mit denen du sie verwenden kannst.

Im nächsten Teil des Buches werden wir uns mit Anweisungen befassen, aber wir werden feststellen, dass sie hauptsächlich dazu dienen, die Objekte zu verwalten, die wir hier kennenlernen. Wenn wir im OOP-Teil dieses Buches zu den Klassen kommen, werden wir außerdem feststellen, dass wir mit ihnen eigene neue Objekttypen definieren können, indem wir die Objekttypen, die wir hier erforschen, sowohl verwenden als auch nachbilden. Aus diesem Grund sind die eingebauten Objekte ein obligatorischer Ausgangspunkt für alle Reisen mit Python .

Hinweis

Traditionelle Einführungen in die Programmierung betonen oft die drei Säulen der Abfolge ("Tu dies, dann das"), der Auswahl ("Tu dies, wenn das wahr ist") und der Wiederholung ("Tu dies viele Male"). Python verfügt über Werkzeuge für alle drei Kategorien sowie einige für die Definition vonFunktionen und Klassen. Diese Themen können dir helfen, dein Denken frühzeitig zu organisieren, aber sie sind etwas künstlich und vereinfachend. Ausdrücke wie "comprehensions" zum Beispiel sind sowohl Wiederholung als auch Auswahl; einige dieser Begriffe haben in Python andere Bedeutungen; und viele spätere Konzepte scheinen überhaupt nicht in diese Form zu passen. In Python sind Objekte und das, was wir mit ihnen tun können, das stärkste vereinheitlichende Prinzip. Lies weiter, um zu sehen, warum.

Warum eingebaute Typen verwenden?

Wenn du bereits mit niedrigeren Sprachen wie C oder C++ gearbeitet hast, weißt du, dass ein großer Teil deiner Arbeit darin besteht, Objektezu implementieren -auch bekannt als Datenstrukturen -,die die Komponenten in deiner Anwendungsdomäne darstellen. Du musst Speicherstrukturen anlegen, die Speicherzuweisung verwalten, Such- und Zugriffsroutinen implementieren und so weiter. Diese Aufgaben sind genauso mühsam (und fehleranfällig), wie sie klingen, und lenken meist von den eigentlichen Zielen deines Programms ab.

In typischen Python-Programmen entfällt ein Großteil dieser Arbeit. Da Python leistungsstarke Objekttypen als festen Bestandteil der Sprache bereitstellt, ist es normalerweise nicht nötig, Objektimplementierungen zu programmieren, bevor du mit der Lösung von Problemen beginnst. Wenn du nicht gerade eine spezielle Verarbeitung brauchst, die die eingebauten Typen nicht bieten, ist es fast immer besser, ein eingebautes Objekt zu verwenden, als ein eigenes zu implementieren. Hier sind einige Gründe dafür:

  • Eingebaute Objekte machen es einfach, Programme zu schreiben. Für einfache Aufgaben sind eingebaute Typen oft alles, was du brauchst, um die Struktur von Problemdomänen darzustellen. Weil du mächtige Werkzeuge wie Sammlungen (Listen) und Suchtabellen (Wörterbücher) kostenlos bekommst, kannst du sie sofort einsetzen. Allein mit den eingebauten Objekttypen von Python kannst du eine Menge Arbeit erledigen.

  • Eingebaute Objekte sind Komponenten von Erweiterungen. Für komplexere Aufgaben musst du vielleicht deine eigenen Objekte mithilfe von Python-Klassen oder C-Sprachschnittstellen erstellen. Aber wie du in späteren Teilen dieses Buches sehen wirst, bauen manuell implementierte Objekte oft auf eingebauten Typen wie Listen und Wörterbüchern auf. Eine Stack-Datenstruktur kann zum Beispiel als Klasse implementiert werden, die eine eingebaute Liste verwaltet oder anpasst.

  • Eingebaute Objekte sind oft effizienter als eigene Datenstrukturen. Die in Python eingebauten Typen verwenden bereits optimierte Algorithmen für die Datenstruktur, die aus Geschwindigkeitsgründen in C implementiert sind. Obwohl du ähnliche Objekttypen auch selbst schreiben kannst, wirst du in der Regel kaum die Leistung der eingebauten Objekttypen erreichen können.

  • Eingebaute Objekte sind ein Standardbestandteil der Sprache. In gewisser Weise lehnt sich Python sowohl an Sprachen an, die sich auf eingebaute Werkzeuge verlassen (z. B. LISP), als auch an Sprachen, die sich darauf verlassen, dass der Programmierer eigene Werkzeugimplementierungen oder Frameworks bereitstellt (z. B. C++). Obwohl du in Python eigene Objekttypen implementieren kannst, musst du das nicht unbedingt tun, um loszulegen. Außerdem sind die eingebauten Werkzeuge von Python standardmäßig immer gleich; proprietäre Frameworks hingegen unterscheiden sich oft von Website zu Website.

Mit anderen Worten: Eingebaute Objekttypen machen das Programmieren nicht nur einfacher, sondern sie sind auch leistungsfähiger und effizienter als das meiste, was von Grund auf neu erstellt werden kann. Unabhängig davon, ob du neue Objekttypen implementierst, bilden die eingebauten Objekte den Kern jedes Python-Programms.

Die wichtigsten Datentypen von Python

Tabelle 4-1 gibt einen Überblick über die in Python eingebauten Objekttypen und einige der Syntax, mit der ihre Literalecodiert werden - alsodie Ausdrücke, die diese Objekte erzeugen.2 Einige dieser Typen werden dir wahrscheinlich bekannt vorkommen, wenn du schon andere Sprachen benutzt hast. So stehen beispielsweise Zahlen und Strings für numerische bzw. textuelle Werte und Datei-Objekte bieten eine Schnittstelle zur Verarbeitung realer Dateien, die auf deinem Computer gespeichert sind.

Für einige Leserinnen und Leser mögen die Objekttypen in Tabelle 4-1 jedoch allgemeiner und mächtiger sein, als du es gewohnt bist. Du wirst zum Beispiel feststellen, dass Listen und Wörterbücher allein schon mächtige Werkzeuge zur Datendarstellung sind, die dir die meiste Arbeit ersparen, die du zur Unterstützung von Sammlungen und zum Suchen in niedrigeren Sprachen benötigst. Listen stellen geordnete Sammlungen anderer Objekte dar, während Wörterbücher Objekte nach Schlüsseln speichern. Sowohl Listen als auch Wörterbücher können verschachtelt werden, sie können bei Bedarf wachsen und schrumpfen und sie können Objekte jeden Typs enthalten.

Tabelle 4-1. Vorschau integrierter Objekte
ObjekttypBeispiel Literale/Kreation

Anzahlbers

1234, 3.1415, 3+4j, 0b111, Decimal(), Fraction()

Strings

'spam', "Bob's", b'a\x01c', u'sp\xc4m'

Lists

[1, [2, 'three'], 4.5], list(range(10))

Diktationäre

{'food': 'spam', 'taste': 'yum'}, dict(hours=10)

Tuples

(1, 'spam', 4, 'U'), tuple('spam'), namedtuple

Files

open('eggs.txt'), open(r'C:\ham.bin', 'wb')

s einstellen

set('abc'), {'a', 'b', 'c'}

Andere Kerntypen

Boolesche Werte, Typen, None

Programmeinheit Typen

Funktionen, Module, Klassen (Teil IV, Teil V, Teil VI)

Umsetzungsbezogene Typen

Kompilierter Code, Stack Tracebacks(Teil IV, Teil VII)

Die Tabelle 4-1 zeigt auch, dass Programmeinheiten wie Funktionen, Module und Klassen - die wir in späteren Teilen dieses Buches kennenlernen werden - in Python ebenfalls Objekte sind. Sie werden mit Anweisungen und Ausdrücken wie def, class, import und lambda erstellt und können frei in Skripten weitergegeben, in anderen Objekten gespeichert werden usw. Python bietet auch eine Reihe von implementierungsbezogenen Typen, wie z. B. kompilierte Code-Objekte, die in der Regel eher für Tool-Builder als für Anwendungsentwickler interessant sind.

Trotz ihres Titels ist Tabelle 4-1 nicht wirklich vollständig, denn alles, was wir in Python-Programmen verarbeiten, ist eine Art Objekt. Wenn wir zum Beispiel Textmuster in Python abgleichen, erstellen wir Musterobjekte, und wenn wir Netzwerkskripte erstellen, verwenden wir Socket-Objekte. Diese anderen Arten von Objekten werden in der Regel durch den Import und die Verwendung von Funktionen in Bibliotheksmodulen erstellt - zum Beispiel in den Modulen re und socket für Muster und Sockets - und haben ein ganz eigenes Verhalten.

Die anderen Objekttypen in Tabelle 4-1 bezeichnen wir in der Regel als Kerndatentypen, weil sie praktisch in die Python-Sprache eingebaut sind, d.h. es gibt eine spezielle Ausdruckssyntax für die meisten von ihnen. Wenn du zum Beispiel den folgenden Code mit in Anführungszeichen gesetzten Zeichen ausführst:

>>> 'spam'

führst du, technisch gesehen, einen literalen Ausdruck aus, der ein neues String-Objekt erzeugt und zurückgibt. Es gibt eine spezielle Python-Syntax, um dieses Objekt zu erzeugen. Ein Ausdruck in eckigen Klammern erzeugt eine Liste, ein Ausdruck in geschweiften Klammern ein Wörterbuch, und so weiter. Auch wenn es, wie wir sehen werden, in Python keine Typendeklarationen gibt, bestimmt die Syntax der Ausdrücke, die du ausführst, die Typen der Objekte, die du erstellst und verwendest. Tatsächlich sind Ausdrücke zur Objekterzeugung wie die in Tabelle 4-1 in der Regel der Ursprung der Typen in der Sprache Python.

Genauso wichtig ist, dass du, sobald du ein Objekt erstellt hast, seine Operationen für alle Zeiten festlegst - du kannst nur String-Operationen mit einem String und Listen-Operationen mit einer Liste durchführen. Formal ausgedrückt bedeutet das, dass Python dynamisch typisiert ist, ein Modell, das automatisch die Typen für dich im Auge behält, anstatt Deklarationscode zu erfordern. ist aber auch stark typisiert, eine Einschränkung, die bedeutet, dass du mit einem Objekt nur Operationen durchführen kannst, die für seinen Typ gültig sind.

Wir werden jeden der Objekttypen in Tabelle 4-1 in den nächsten Kapiteln im Detail untersuchen. Bevor wir uns jedoch in die Details vertiefen, werfen wir zunächst einen kurzen Blick auf die Kernobjekte von Python in Aktion. Der Rest dieses Kapitels gibt einen Vorgeschmack auf die Operationen, die wir in den folgenden Kapiteln ausführlicher behandeln werden. Erwarte nicht, dass du hier alles erfährst - dieses Kapitel soll nur deinen Appetit anregen und einige wichtige Ideen vorstellen. Aber der beste Weg, um loszulegen, ist, gleich mit richtigem Code loszulegen.

Zahlen

Wenn du in der Vergangenheit programmiert oder Skripte geschrieben hast, werden dir einige der Objekttypen in Tabelle 4-1 wahrscheinlich bekannt vorkommen. Auch wenn du das nicht tust, sind Zahlen ziemlich einfach. Pythons Kernobjektsatz enthält die üblichen Verdächtigen: Ganzzahlen, die keinen Bruchteil haben, Fließkommazahlen , die einen Bruchteil haben, und exotischere Typen -komplexe Zahlen mit Imaginärteilen, Dezimalzahlenmit fester Genauigkeit, rationale Zahlen mit Zähler und Nenner und Mengen mit vollem Funktionsumfang. Die eingebauten Zahlen reichen aus, um die meisten numerischen Größen darzustellen - von deinem Alter bis zu deinem Kontostand -, aber weitere Typen sind als Zusatzmodule von Drittanbietern erhältlich.

Obwohl Python einige ausgefeiltere Optionen bietet, sind die grundlegenden Zahlentypen von Python, nun ja, grundlegend. Die Zahlen in Python unterstützen die normalen mathematischen Operationen. Zum Beispiel führt das Pluszeichen (+) die Addition durch, ein Stern (*) wird für die Multiplikation verwendet und zwei Sterne (**) werden für die Potenzierung verwendet:

>>> 123 + 222                    # Integer addition
345
>>> 1.5 * 4                      # Floating-point multiplication
6.0
>>> 2 ** 100                     # 2 to the power 100, again
1267650600228229401496703205376

Beachte das letzte Ergebnis hier: Der Integer-Typ von Python 3.X bietet bei Bedarf automatisch eine zusätzliche Genauigkeit für große Zahlen wie diese (in 2.X gibt es einen separaten Long-Integer-Typ, der Zahlen, die zu groß für den normalen Integer-Typ sind, auf ähnliche Weise behandelt). Du kannst zum Beispiel 2 hoch 1.000.000 als Ganzzahl in Python berechnen, aber du solltest nicht versuchen, das Ergebnis auszudrucken - bei mehr als 300.000 Ziffern könntest du eine Weile warten!

>>> len(str(2 ** 1000000))       # How many digits in a really BIG number?
301030

Diese verschachtelte Form arbeitet von innen nach außen - zuerst wird die Zahl des ** Ergebnisses mit der eingebauten str Funktion in eine Zeichenkette umgewandelt und dann die Länge der resultierenden Zeichenkette mit len ermittelt. Das Endergebnis ist die Anzahl der Ziffern. str und len funktionieren mit vielen Objekttypen; mehr zu beiden im weiteren Verlauf.

Wenn du bei Pythons vor 2.7 und 3.1 anfängst, mit Fließkommazahlen zu experimentieren, wirst du wahrscheinlich über etwas stolpern, das auf den ersten Blick ein bisschen seltsam aussieht:

>>> 3.1415 * 2                   # repr: as code (Pythons < 2.7 and 3.1)
6.2830000000000004
>>> print(3.1415 * 2)            # str: user-friendly
6.283

Das erste Ergebnis ist kein Fehler, sondern ein Darstellungsproblem. Es gibt zwei Möglichkeiten, jedes Objekt in Python auszudrucken - mit voller Präzision (wie im ersten hier gezeigten Ergebnis) und in einer benutzerfreundlichen Form (wie im zweiten). Die erste Form wird als as-code repr bezeichnet, die zweite ist die benutzerfreundliche str. In älteren Pythons zeigt die Fließkommazahl repr manchmal eine höhere Genauigkeit an, als du vielleicht erwartest. Der Unterschied kann auch von Bedeutung sein, wenn wir dazu übergehen, Klassen zu verwenden. Wenn dir etwas seltsam vorkommt, kannst du es zunächst mit einem print Funktionsaufruf zeigen.

Noch besser ist es, wenn du auf Python 2.7 und die neueste Version 3.X aktualisierst. Dort werden Fließkommazahlen intelligenter dargestellt, in der Regel mit weniger überflüssigen Ziffern - da dieses Buch auf Python 2.7 und 3.3 basiert, ist dies die Darstellungsform, die ich in diesem Buch für Fließkommazahlen verwenden werde:

>>> 3.1415 * 2                   # repr: as code (Pythons >= 2.7 and 3.1)
6.283

Neben Ausdrücken gibt es eine Handvoll nützlicher numerischer Module, die mit Python ausgeliefert werden - Module sind einfach Pakete mit zusätzlichen Tools, die wir importieren, um sie zu verwenden:

>>> import math
>>> math.pi
3.141592653589793
>>> math.sqrt(85)
9.219544457292887

Das Modul math enthält fortgeschrittene numerische Werkzeuge in Form von Funktionen, während das Modul random Zufallszahlen erzeugt und Zufallsauswahlen durchführt (hier aus einer in eckigen Klammern kodierten Python-Liste - einer geordneten Sammlung anderer Objekte, die später in diesem Kapitel vorgestellt werden):

>>> import random
>>> random.random()
0.7082048489415967
>>> random.choice([1, 2, 3, 4])
1

Python enthält auch exotischere numerische Objekte - wie komplexe, feststehende und rationale Zahlen sowie Mengen und Boolesche Verknüpfungen - und die Open-Source-Erweiterungsdomäne von Drittanbietern bietet noch mehr (z. B. Matrizen und Vektoren sowie Zahlen mit erweiterter Genauigkeit). Wir verschieben die Diskussion über diese Typen auf einen späteren Zeitpunkt in diesem Kapitel und im Buch.

Bisher haben wir Python eher wie einen einfachen Taschenrechner benutzt; um den eingebauten Typen besser gerecht zu werden, wollen wir uns nun mit strings beschäftigen.

Strings

Strings sind und werden verwendet, um sowohl Textinformationen (z. B. deinen Namen) als auch beliebige Sammlungen von Bytes (z. B. den Inhalt einer Bilddatei) aufzuzeichnen. Sie sind unser erstes Beispiel für das, was wir in Python eine Sequenznennen - einepositionsmäßig geordnete Sammlung anderer Objekte. Sequenzen ordnen die in ihnen enthaltenen Objekte von links nach rechts: Die Objekte werden anhand ihrer relativen Position gespeichert und abgerufen. Streng genommen sind Strings Sequenzen aus einstelligen Zeichenfolgen; andere, allgemeinere Sequenztypen sind Listen und Tupel, die später behandelt werden.

Reihenfolge der Operationen

Als Sequenzen unterstützen Zeichenketten Operationen, die eine positionsbezogene Reihenfolge der Elemente voraussetzen. Wenn wir zum Beispiel eine vierstellige Zeichenkette in Anführungszeichen kodiert haben (in der Regel eine einfache Zeichenkette), können wir ihre Länge mit der eingebauten Funktion len überprüfen und ihre Komponenten mit Indizierungsausdrücken abrufen:

>>> S = 'Spam'           # Make a 4-character string, and assign it to a name
>>> len(S)               # Length
4
>>> S[0]                 # The first item in S, indexing by zero-based position
'S'
>>> S[1]                 # The second item from the left
'p'

In Python werden Indizes als Offsets von vorne kodiert und beginnen daher bei 0: Das erste Element steht bei Index 0, das zweite bei Index 1 und so weiter.

Beachte, dass wir die Zeichenkette hier einer Variablen namens S zuweisen. Wir werden später (vor allem in Kapitel 6) genauer erklären, wie das funktioniert, aber Python-Variablen müssen nie im Voraus deklariert werden. Eine Variable wird erstellt, wenn du ihr einen Wert zuweist. Sie kann jedem Objekttyp zugewiesen werden und wird durch ihren Wert ersetzt, wenn sie in einem Ausdruck auftaucht. Außerdem muss sie zu dem Zeitpunkt, an dem du ihren Wert verwendest, bereits zugewiesen worden sein. Für die Zwecke dieses Kapitels reicht es aus zu wissen, dass wir einer Variablen ein Objekt zuweisen müssen, um es für eine spätere Verwendung zu speichern.

In Python können wir auch rückwärts indizieren, und zwar vom Ende her - positive Indizes zählen von links und negative Indizes von rechts:

>>> S[-1]                # The last item from the end in S
'm'
>>> S[-2]                # The second-to-last item from the end
'a'

Formal gesehen wird ein negativer Index einfach zur Länge der Zeichenkette addiert, sodass die folgenden beiden Operationen gleichwertig sind (obwohl die erste einfacher zu kodieren ist und weniger leicht falsch gemacht werden kann):

>>> S[-1]                # The last item in S
'm'
>>> S[len(S)-1]          # Negative indexing, the hard way
'm'

Beachte, dass wir mit einen beliebigen Ausdruck in den eckigen Klammern verwenden können, nicht nur ein fest codiertes Zahlenliteral - überall dort, wo Python einen Wert erwartet, können wir ein Literal, eine Variable oder einen beliebigen Ausdruck verwenden. Auf diese Weise ist die Syntax von Python völlig allgemein.

Neben der einfachen Positionsindizierung unterstützen Sequenzen auch eine allgemeinere Form der Indizierung, die als Slicing bekannt ist und mit der ein ganzer Abschnitt (Slice) in einem einzigen Schritt extrahiert werden kann. Ein Beispiel:

>>> S                     # A 4-character string
'Spam'
>>> S[1:3]                # Slice of S from offsets 1 through 2 (not 3)
'pa'

Der einfachste Weg, sich Slices vorzustellen, ist wohl, dass sie eine Möglichkeit sind, eine ganze Spalte in einem einzigen Schritt aus einem String zu extrahieren. Ihre allgemeine Form, X[I:J], bedeutet: "Gib mir alles in X vom Offset I bis zum Offset J." Das Ergebnis wird in einem neuen Objekt zurückgegeben. Die zweite der vorangegangenen Operationen liefert uns zum Beispiel alle Zeichen in der Zeichenkette S von Offset 1 bis 2 (also 1 bis 3 - 1) als neue Zeichenkette. Der Effekt ist, dass die beiden Zeichen in der Mitte herausgeschnitten oder "geparst" werden.

In einem Slice ist die linke Grenze standardmäßig Null und die rechte Grenze ist standardmäßig die Länge der zu slicenden Sequenz. Dies führt zu einigen häufigen Anwendungsvarianten:

>>> S[1:]                 # Everything past the first (1:len(S))
'pam'
>>> S                     # S itself hasn't changed
'Spam'
>>> S[0:3]                # Everything but the last
'Spa'
>>> S[:3]                 # Same as S[0:3]
'Spa'
>>> S[:-1]                # Everything but the last again, but simpler (0:-1)
'Spa'
>>> S[:]                  # All of S as a top-level copy (0:len(S))
'Spam'

Beachte im vorletzten Befehl, wie negative Offsets verwendet werden können, um auch für Slices Grenzen zu setzen, und wie die letzte Operation effektiv die gesamte Zeichenkette kopiert. Wie du später lernen wirst, gibt es keinen Grund, eine Zeichenkette zu kopieren, aber diese Form kann für Sequenzen wie Listen nützlich sein.

Als Sequenzen unterstützen Strings auch die Verkettung mit dem Pluszeichen (Zusammenfügen zweier Strings zu einem neuen String) und die Wiederholung (Erstellen eines neuen Strings durch Wiederholung eines anderen):

>>> S
'Spam'
>>> S + 'xyz'             # Concatenation
'Spamxyz'
>>> S                     # S is unchanged
'Spam'
>>> S * 8                 # Repetition
'SpamSpamSpamSpamSpamSpamSpamSpam'

Beachte, dass das Pluszeichen (+) für verschiedene Objekte unterschiedliche Bedeutungen hat: Addition für Zahlen und Verkettung für Strings. Dies ist eine allgemeine Eigenschaft von Python, die wir später in diesem Buch als Polymorphismus bezeichnen werden - in der Summe hängt die Bedeutung einer Operation von den Objekten ab, auf denen sie ausgeführt wird. Wie du sehen wirst, wenn wir uns mit der dynamischen Typisierung beschäftigen, ist diese Polymorphie-Eigenschaft für einen Großteil der Prägnanz und Flexibilität von Python-Code verantwortlich. Da die Typen nicht eingeschränkt sind, kann eine in Python kodierte Operation normalerweise automatisch mit vielen verschiedenen Objekttypen funktionieren, solange sie eine kompatible Schnittstelle unterstützen (wie hier die Operation +). Dies ist eine wichtige Idee in Python; du wirst später auf unserer Tour mehr darüber erfahren.

Unveränderlichkeit

Beachte auch in , dass wir die ursprüngliche Zeichenkette mit keiner der Operationen, die wir auf ihr ausgeführt haben, verändert haben. Jede String-Operation ist so definiert, dass sie einen neuen String als Ergebnis erzeugt, denn Strings sind in Python unveränderlich - sie können nicht verändert werden, nachdem sie erstellt wurden. Mit anderen Worten: Du kannst die Werte von unveränderlichen Objekten nicht überschreiben. Du kannst zum Beispiel eine Zeichenkette nicht ändern, indem du sie einer ihrer Positionen zuweist, aber du kannst immer eine neue Zeichenkette erstellen und ihr denselben Namen zuweisen. Da Python alte Objekte nach und nach aufräumt (wie du später noch sehen wirst), ist das nicht so ineffizient, wie es vielleicht klingt:

>>> S
'Spam'

>>> S[0] = 'z'             # Immutable objects cannot be changed
...error text omitted...
TypeError: 'str' object does not support item assignment

>>> S = 'z' + S[1:]        # But we can run expressions to make new objects
>>> S
'zpam'

Jedes Objekt in Python wird entweder als unveränderlich (unveränderbar) oder als unveränderlich eingestuft. Was die Kerntypen angeht, sind Zahlen, Strings und Tupel unveränderlich; Listen, Wörterbücher und Mengen sind es nicht - sie können an Ort und Stelle frei verändert werden, genauso wie die meisten neuen Objekte, die du mit Klassen programmierst. Diese Unterscheidung ist für die Arbeit mit Python von entscheidender Bedeutung, und zwar in einer Weise, die wir noch nicht vollständig erforschen können. Die Unveränderlichkeit kann unter anderem dazu verwendet werden, um zu garantieren, dass ein Objekt während des gesamten Programms konstant bleibt; die Werte von veränderbaren Objekten können jederzeit und überall geändert werden (und zwar unabhängig davon, ob du es erwartest oder nicht).

Streng genommen kannst du textbasierte Daten an Ort und Stelle ändern, wenn du sie entweder in eine Liste einzelner Zeichen auflöst und wieder zusammenfügst, ohne dass etwas dazwischen liegt, oder wenn du den neueren Typ bytearray verwendest, der in Pythons 2.6, 3.0 und später verfügbar ist:

>>> S = 'shrubbery'
>>> L = list(S)                                     # Expand to a list: [...]
>>> L
['s', 'h', 'r', 'u', 'b', 'b', 'e', 'r', 'y']
>>> L[1] = 'c'                                      # Change it in place
>>> ''.join(L)                                      # Join with empty delimiter
'scrubbery'

>>> B = bytearray(b'spam')                          # A bytes/list hybrid (ahead)
>>> B.extend(b'eggs')                               # 'b' needed in 3.X, not 2.X
>>> B                                               # B[i] = ord(x) works here too
bytearray(b'spameggs')
>>> B.decode()                                      # Translate to normal string
'spameggs'

bytearray unterstützt In-Place-Änderungen für Text, aber nur für Text, dessen Zeichen alle höchstens 8 Bit breit sind (z. B. ASCII). Alle anderen Zeichenketten sind nach wie vor unveränderlich -bytearray ist eine Mischung aus unveränderlichen Byte-Zeichenketten(deren b'...' Syntax in 3.X erforderlich und in 2.X optional ist) und veränderlichen Listen (kodiert und angezeigt in []). Wir müssen mehr über diese beiden und Unicode-Text lernen, um diesen Code vollständig zu verstehen.

Typspezifische Methoden

Jede String-Operation die wir bisher kennengelernt haben, ist eigentlich eine Sequenz-Operation - das heißt, diese Operationen funktionieren auch mit anderen Sequenzen in Python, einschließlich Listen und Tupeln. Zusätzlich zu den allgemeinen Sequenzoperationen haben Strings aber auch eigene Operationen, die als Methodenverfügbar sind - Funktionen, die an ein bestimmtes Objekt angehängt sind und auf dieses wirken.

Zum Beispiel ist die Methode string find die grundlegende Teilzeichensuche (sie gibt den Offset der übergebenen Teilzeichenkette zurück oder −1, wenn sie nicht vorhanden ist), und die Methode string replaceführt globale Suchen und Ersetzungen durch; beide wirken auf das Subjekt, an das sie angehängt sind und von dem sie aufgerufen werden:

>>> S = 'Spam'
>>> S.find('pa')                 # Find the offset of a substring in S
1
>>> S
'Spam'
>>> S.replace('pa', 'XYZ')       # Replace occurrences of a string in S with another
'SXYZm'
>>> S
'Spam'

Trotz der Namen dieser String-Methoden ändern wir hier nicht die ursprünglichen Strings, sondern erstellen neue Strings als Ergebnisse - da Strings unveränderlich sind, ist dies die einzige Möglichkeit, wie das funktionieren kann. String-Methoden sind die erste Reihe der Textverarbeitungswerkzeuge in Python. Andere Methoden teilen eine Zeichenkette anhand eines Trennzeichens in Teilstrings auf (praktisch als einfache Form des Parsens), führen Groß- und Kleinschreibungskonvertierungen durch, prüfen den Inhalt der Zeichenkette (Ziffern, Buchstaben usw.) und entfernen Leerzeichen an den Enden der Zeichenkette:

>>> line = 'aaa,bbb,ccccc,dd'
>>> line.split(',')              # Split on a delimiter into a list of substrings
['aaa', 'bbb', 'ccccc', 'dd']

>>> S = 'spam'
>>> S.upper()                    # Upper- and lowercase conversions
'SPAM'
>>> S.isalpha()                  # Content tests: isalpha, isdigit, etc.
True

>>> line = 'aaa,bbb,ccccc,dd\n'
>>> line.rstrip()                # Remove whitespace characters on the right side
'aaa,bbb,ccccc,dd'
>>> line.rstrip().split(',')     # Combine two operations
['aaa', 'bbb', 'ccccc', 'dd']

Beachte den letzten Befehl hier - er entfernt, bevor er geteilt wird, weil Python von links nach rechts läuft und dabei ein temporäres Ergebnis erzeugt. Strings unterstützen auch eine erweiterte Ersetzungsoperation , die als Formatierung bekannt ist und sowohl als Ausdruck (das Original) als auch als String-Methodenaufruf (neu ab 2.6 und 3.0) zur Verfügung steht; bei letzterem kannst du ab 2.7 und 3.1 relative Argumentwerte weglassen:

>>> '%s, eggs, and %s' % ('spam', 'SPAM!')          # Formatting expression (all)
'spam, eggs, and SPAM!'

>>> '{0}, eggs, and {1}'.format('spam', 'SPAM!')    # Formatting method (2.6+, 3.0+)
'spam, eggs, and SPAM!'

>>> '{}, eggs, and {}'.format('spam', 'SPAM!')      # Numbers optional (2.7+, 3.1+)
'spam, eggs, and SPAM!'

Die Formatierung ist reich an Funktionen, auf die wir erst später in diesem Buch eingehen werden und die vor allem dann wichtig sind, wenn du numerische Berichte erstellen musst:

>>> '{:,.2f}'.format(296999.2567)                   # Separators, decimal digits
'296,999.26'
>>> '%.2f | %+05d' % (3.14159, −42)                 # Digits, padding, signs
'3.14 | −0042'

Ein Hinweis: Obwohl Sequenzoperationen generisch sind, sind es Methoden nicht - auch wenn einige Typen einige Methodennamen gemeinsam haben, funktionieren String-Methodenoperationen in der Regel nur mit Strings und mit nichts anderem. Als Faustregel kann man sagen, dass Pythons Werkzeugsatz mehrschichtig ist: Allgemeine Operationen, die mehrere Typen umfassen, werden als eingebaute Funktionen oder Ausdrücke angezeigt (z. B. len(X), X[0]), aber typspezifische Operationen sind Methodenaufrufe (z. B. aString.upper()). Die Werkzeuge, die du brauchst, werden mit zunehmender Verwendung von Python immer selbstverständlicher, aber im nächsten Abschnitt findest du ein paar Tipps, die du schon jetzt nutzen kannst.

Hilfe bekommen

Die im vorherigen Abschnitt vorgestellten Methoden sind eine repräsentative, wenn auch kleine Auswahl dessen, was für String-Objekte verfügbar ist. Im Allgemeinen ist dieses Buch nicht erschöpfend, wenn es um Objektmethoden geht. Für weitere Details kannst du jederzeit die eingebaute Funktion dir aufrufen. Diese Funktion listet die Variablen auf, die im Bereich des Aufrufers zugewiesen sind, wenn sie ohne Argument aufgerufen wird; außerdem gibt sie eine Liste aller Attribute zurück, die für jedes übergebene Objekt verfügbar sind. Da Methoden Funktionsattribute sind, tauchen sie in dieser Liste auf. Angenommen, S ist immer noch ein String, dann sind hier seine Attribute unter Python 3.3 (Python 2.X weicht leicht ab):

>>> dir(S)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__',
'__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count',
'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index',
'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower',
'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust',
'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex',
'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Die Namen mit doppelten Unterstrichen in dieser Liste werden dich wahrscheinlich erst später im Buch interessieren, wenn wir uns mit der Überladung von Operatoren in Klassen beschäftigen - sie stellen die Implementierung des String-Objekts dar und sind verfügbar, um Anpassungen zu unterstützen. Die __add__ Methode von Strings ist zum Beispiel die eigentliche Verkettung; Python bildet die erste der folgenden Methoden intern auf die zweite ab, obwohl du die zweite Form normalerweise nicht selbst verwenden solltest (sie ist weniger intuitiv und könnte sogar langsamer sein):

>>> S + 'NI!'
'spamNI!'
>>> S.__add__('NI!')
'spamNI!'

Im Allgemeinen sind führende und nachgestellte doppelte Unterstriche das Namensmuster, das Python für Implementierungsdetails verwendet. Die Namen ohne Unterstriche in dieser Liste sind die aufrufbaren Methoden für String-Objekte.

Die Funktion dir nennt einfach die Namen der Methoden. Wenn du wissen willst, was sie tun, kannst du sie an die Funktion help übergeben:

>>> help(S.replace)
Help on built-in function replace:

replace(...)
    S.replace(old, new[, count]) -> str

    Return a copy of S with all occurrences of substring
    old replaced by new.  If the optional argument count is
    given, only the first count occurrences are replaced.

help ist eine der wenigen Schnittstellen zu einem Codesystem, das mit Python ausgeliefert wird undals PyDoc bekannt ist - einWerkzeug zum Extrahieren von Dokumentation aus Objekten. Später in diesem Buch wirst du sehen, dass PyDoc seine Berichte auch im HTML-Format für die Anzeige in einem Webbrowser darstellen kann.

Du kannst auch nach Hilfe für eine ganze Zeichenkettefragen (z.B. help(S)), aber du bekommst vielleicht mehr oder weniger Hilfe, als du sehen willst - in älteren Pythons gibt es Informationen zu jeder String-Methode, in neueren Versionen wahrscheinlich gar keine Hilfe, weil Strings besonders behandelt werden. Im Allgemeinen ist es besser, nach einer bestimmten Methode zu fragen.

Sowohl dir als auch help akzeptieren als Argumente entweder ein reales Objekt (wie unsere Zeichenkette S), oder den Namen eines Datentyps (wie str, list und dict). Die letztere Form gibt dieselbe Liste wie dir zurück, zeigt aber die vollständigen Typangaben für help an und ermöglicht es dir, über den Typnamen nach einer bestimmten Methode zu fragen (z. B. Hilfe zu str.replace).

Für weitere Details kannst du auch das Referenzhandbuch der Standardbibliothek von Python oder kommerziell veröffentlichte Referenzbücher zu Rate ziehen, aber dir und help sind die erste Ebene der Dokumentation in Python.

Andere Möglichkeiten, Strings zu kodieren

Bisher haben wir uns die Sequenzoperationen und typspezifischen Methoden des String-Objekts angesehen. Python bietet uns auch eine Vielzahl von Möglichkeiten, Strings zu kodieren, die wir später noch genauer untersuchen werden. Sonderzeichen können zum Beispiel als Backslash-Escape-Sequenzen dargestellt werden, die Python in \xNN hexadezimaler Escape-Schreibweise anzeigt, sofern es sich nicht um druckbare Zeichen handelt:

>>> S = 'A\nB\tC'            # \n is end-of-line, \t is tab
>>> len(S)                   # Each stands for just one character
5

>>> ord('\n')                # \n is one character coded as decimal value 10
10

>>> S = 'A\0B\0C'            # \0, a binary zero byte, does not terminate string
>>> len(S)
5
>>> S                        # Non-printables are displayed as \xNN hex escapes
'A\x00B\x00C'

In Python können Zeichenketten in einfache oder doppelte Anführungszeichen eingeschlossen werden - sie bedeuten dasselbe, aber die andere Art von Anführungszeichen kann ohne Escape-Zeichen eingebettet werden (die meisten Programmierer bevorzugen einfache Anführungszeichen). Außerdem können mehrzeilige String-Literale in dreifache Anführungszeichen (einfach oder doppelt) eingeschlossen werden - wenn diese Form verwendet wird, werden alle Zeilen zusammengefügt und Zeilenendezeichen an den Stellen hinzugefügt, an denen Zeilenumbrüche auftreten. Dies ist eine kleine syntaktische Erleichterung, aber sie ist nützlich, um z. B. mehrzeiligen HTML-, XML- oder JSON-Code in ein Python-Skript einzubetten und Codezeilen vorübergehend auszulagern - füge einfach drei Anführungszeichen oben und unten hinzu:

>>> msg = """
aaaaaaaaaaaaa
bbb'''bbbbbbbbbb""bbbbbbb'bbbb
cccccccccccccc
"""
>>> msg
'\naaaaaaaaaaaaa\nbbb\'\'\'bbbbbbbbbb""bbbbbbb\'bbbb\ncccccccccccccc\n'

Python unterstützt auch ein Raw-String-Literal, das den Backslash-Escape-Mechanismus ausschaltet. Solche Literale beginnen mit dem Buchstaben r und sind nützlich für Zeichenketten wie Verzeichnispfade unter Windows (z. B. r'C:\text\new').

Unicode Zeichenketten

Pythons Strings bieten außerdem die volle Unicode-Unterstützung, die für die Verarbeitung von Text in internationalisierten Zeichensätzen erforderlich ist. Zeichen des japanischen und russischen Alphabets zum Beispiel liegen außerhalb des ASCII-Sets. Solcher Nicht-ASCII-Text kann in Webseiten, E-Mails, GUIs, JSON, XML oder anderswo auftauchen. Wenn dies der Fall ist, ist für eine gute Handhabung Unicode-Unterstützung erforderlich. Python hat eine solche Unterstützung eingebaut, aber die Form der Unicode-Unterstützung variiert je nach Python-Zeile und ist einer der größten Unterschiede.

In Python 3.X behandelt der normale str String Unicode-Text (einschließlich ASCII, was nur eine einfache Art von Unicode ist); ein eigener bytes String-Typ repräsentiert rohe Byte-Werte (einschließlich Medien und kodierten Text); und 2.X Unicode-Literale werden in 3.3 und später für 2.X-Kompatibilität unterstützt (sie werden genauso behandelt wie normale 3.X strStrings):

>>> 'sp\xc4m'                     # 3.X: normal str strings are Unicode text
'spÄm'
>>> b'a\x01c'                     # bytes strings are byte-based data
b'a\x01c'
>>> u'sp\u00c4m'                  # The 2.X Unicode literal works in 3.3+: just str
'spÄm'

In Python 2.X verarbeitet der normale str String sowohl 8-Bit-Zeichenketten (einschließlich ASCII-Text) als auch rohe Byte-Werte; ein eigener unicode String-Typ repräsentiert Unicode-Text; und 3.X-Bytes-Literale werden ab 2.6 aus Gründen der 3.X-Kompatibilität unterstützt (sie werden genauso behandelt wie normale 2.X str Strings):

>>> print u'sp\xc4m'              # 2.X: Unicode strings are a distinct type
spÄm
>>> 'a\x01c'                      # Normal str strings contain byte-based text/data
'a\x01c'
>>> b'a\x01c'                     # The 3.X bytes literal works in 2.6+: just str
'a\x01c'

Formal gesehen sind sowohl in 2.X als auch in 3.X Nicht-Unicode-Strings Sequenzen von 8-Bit-Bytes, die wenn möglich mit ASCII-Zeichen gedruckt werden, und Unicode-Strings sind Sequenzen von Unicode-Codepunkten - identifizierendeZahlen für Zeichen, die nicht unbedingt einzelnen Bytes entsprechen, wenn sie in Dateien kodiert oder im Speicher gespeichert werden. Tatsächlich gilt der Begriff "Byte" nicht für Unicode: Einige Kodierungen enthalten Zeichencodepunkte, die zu groß für ein Byte sind, und selbst einfacher 7-Bit-ASCII-Text wird bei einigen Kodierungen und Speicherverfahren nicht in einem Byte pro Zeichen gespeichert:

>>> 'spam'                        # Characters may be 1, 2, or 4 bytes in memory
'spam'
>>> 'spam'.encode('utf8')         # Encoded to 4 bytes in UTF-8 in files
b'spam'
>>> 'spam'.encode('utf16')        # But encoded to 10 bytes in UTF-16
b'\xff\xfes\x00p\x00a\x00m\x00'

Sowohl 3.X als auch 2.X unterstützen auch den bytearray String-Typ , den wir bereits kennengelernt haben. Dabei handelt es sich im Wesentlichen um einen bytes String ( str in 2.X), der die meisten Operationen zur Änderung des List-Objekts unterstützt, die vor Ort durchgeführt werden können.

Sowohl 3.X als auch 2.X unterstützen auch die Kodierung von Nicht-ASCII-Zeichen mit \x hexadezimalen und kurzen \u und langen \U Unicode-Escapes sowie dateiweite Kodierungen, die in Programmquelldateien deklariert werden. Hier ist unser Nicht-ASCII-Zeichen, das in 3.X auf drei Arten kodiert ist (füge ein führendes "u" hinzu und sage "print", um dasselbe in 2.X zu sehen):

>>> 'sp\xc4\u00c4\U000000c4m'
'spÄÄÄm'

Was diese Werte bedeuten und wie sie verwendet werden, unterscheidet sich zwischen Text-Strings, die in 3.X der normale String und in 2.X Unicode sind, und Byte-Strings, die in 3.X Bytes und in 2.X der normale String sind. Alle diese Escape-Zeichen können verwendet werden, um echte Unicode-Codepunkt-Ordinalwert-Ganzzahlen in Text-Strings einzubetten. Im Gegensatz dazu verwenden Byte-Strings nur \x Hexadezimal-Escapes, um die kodierte Form des Textes einzubetten, nicht aber seine dekodierten Codepunkt-Werte - kodierte Bytes sind dasselbe wie Codepunkte, nur für einige Kodierungen und Zeichen:

>>> '\u00A3', '\u00A3'.encode('latin1'), b'\xA3'.decode('latin1')
('£', b'\xa3', '£')

Ein bemerkenswerter Unterschied ist, dass Python 2.X die Vermischung von normalen und Unicode-Zeichenfolgen in Ausdrücken zulässt, solange die normale Zeichenkette vollständig aus ASCII besteht. Im Gegensatz dazu hat Python 3.X ein strengeres Modell, das die Vermischung von normalen und Byte-Zeichenfolgen ohne explizite Konvertierung nicht zulässt:

u'x' + b'y'            # Works in 2.X (where b is optional and ignored)
u'x' + 'y'             # Works in 2.X: u'xy'

u'x' + b'y'            # Fails in 3.3 (where u is optional and ignored)
u'x' + 'y'             # Works in 3.3: 'xy'

'x' + b'y'.decode()    # Works in 3.X if decode bytes to str: 'xy'
'x'.encode() + b'y'    # Works in 3.X if encode str to bytes: b'xy'

Abgesehen von diesen Zeichenkettentypen beschränkt sich die Unicode-Verarbeitung meist auf die Übertragung von Textdaten in und aus Dateien - Textwird in Bytes kodiert, wenn er in einer Datei gespeichert wird, und in Zeichen (auch bekannt als Codepunkte) dekodiert, wenn er wieder in den Speicher eingelesen wird. Sobald der Text geladen ist, verarbeiten wir ihn normalerweise nur noch in dekodierter Form als Zeichenkette.

Aufgrund dieses Modells sind Dateien in 3.X auch inhaltsspezifisch: Textdateien implementieren benannte Kodierungen und akzeptieren und geben str Zeichenketten zurück, aber Binärdateien handeln stattdessen mit bytesZeichenketten für rohe Binärdaten. In Python 2.X besteht der Inhalt normaler Dateien aus str Bytes, und ein spezielles codecs Modul behandelt Unicode und repräsentiert den Inhalt mit dem Typ unicode.

Wir werden Unicode später in diesem Kapitel bei den Dateien wiedersehen, aber den Rest der Unicode-Geschichte heben wir uns für später in diesem Buch auf. Es taucht kurz in einem Beispiel in Kapitel 25in Verbindung mit Währungssymbolen auf, wird aber größtenteils auf den Teil über fortgeschrittene Themen in diesem Buch verschoben. In einigen Bereichen ist Unicode unverzichtbar, aber viele Programmierer/innen kommen auch mit einer flüchtigen Kenntnis aus. Wenn deine Daten ausschließlich aus ASCII-Text bestehen, sind die String- und Dateigeschichten in 2.X und 3.X weitgehend identisch. Und wenn du neu in der Programmierung bist, kannst du die meisten Unicode-Details getrost aufschieben, bis du die String-Grundlagen beherrschst .

Musterabgleich

Bevor wir weitermachen, sollten wir auf hinweisen, dass keine der Methoden des String-Objekts eine musterbasierte Textverarbeitung unterstützt. Der Abgleich von Textmustern ist ein fortgeschrittenes Werkzeug, das den Rahmen dieses Buches sprengt, aber Leser mit Erfahrung in anderen Skriptsprachen wird es interessieren, dass wir für den Abgleich von Textmustern in Python ein Modul namens re importieren. Dieses Modul hat analoge Aufrufe für das Suchen, Aufteilen und Ersetzen, aber da wir Muster verwenden können, um Teilzeichenfolgen anzugeben, können wir viel allgemeiner vorgehen:

>>> import re
>>> match = re.match('Hello[ \t]*(.*)world', 'Hello    Python world')
>>> match.group(1)
'Python '

In diesem Beispiel wird nach einer Teilzeichenkette gesucht, die mit dem Wort "Hallo" beginnt, gefolgt von null oder mehr Tabulatoren oder Leerzeichen, gefolgt von beliebigen Zeichen, die als übereinstimmende Gruppe gespeichert werden sollen und mit dem Wort "Welt" enden. Wenn eine solche Teilzeichenkette gefunden wird, stehen Teile der Teilzeichenkette, die mit Teilen des Musters in Klammern übereinstimmen, als Gruppen zur Verfügung. Das folgende Muster wählt zum Beispiel drei Gruppen aus, die durch Schrägstriche oder Doppelpunkte getrennt sind, und ähnelt der Aufteilung durch ein alternatives Muster:

>>> match = re.match('[/:](.*)[/:](.*)[/:](.*)', '/usr/home:lumberjack')
>>> match.groups()
('usr', 'home', 'lumberjack')

>>> re.split('[/:]', '/usr/home:lumberjack')
['', 'usr', 'home', 'lumberjack']

Die Mustererkennung ist an sich schon ein fortschrittliches Textverarbeitungswerkzeug, aber Python unterstützt auch noch fortschrittlichere Text- und Sprachverarbeitung, einschließlich XML- und HTML-Parsing und Analyse natürlicher Sprache. Am Ende von Kapitel 37 werden wir weitere kurze Beispiele für Muster und XML-Parsing sehen, aber für dieses Tutorial habe ich bereits genug über Strings gesagt, also lass uns zum nächsten Typ übergehen.

Listen

Das Python Listenobjekt ist die allgemeinste Sequenz, die die Sprache bietet. Listen sind positionell geordnete Sammlungen von Objekten mit beliebigem Typ und haben keine feste Größe. Sie sind außerdem veränderbar - im Gegensatz zu Strings können Listen an Ort und Stelle durch die Zuweisung von Offsets sowie durch eine Vielzahl von Listenmethodenaufrufen verändert werden. Damit sind sie ein sehr flexibles Werkzeug, um beliebige Sammlungen darzustellen - Listen von Dateien in einem Ordner, von Mitarbeitern in einer Firma, von E-Mails in deinem Posteingang und so weiter.

Reihenfolge der Operationen

Da es sich bei um Sequenzen handelt, unterstützen Listen alle Sequenzoperationen, die wir für Strings besprochen haben; der einzige Unterschied besteht darin, dass die Ergebnisse normalerweise Listen statt Strings sind. Nehmen wir zum Beispiel eine Liste mit drei Einträgen:

>>> L = [123, 'spam', 1.23]            # A list of three different-type objects
>>> len(L)                             # Number of items in the list
3

können wir wie bei Zeichenketten indexieren, zerschneiden und so weiter:

>>> L[0]                               # Indexing by position
123
>>> L[:-1]                             # Slicing a list returns a new list
[123, 'spam']

>>> L + [4, 5, 6]                      # Concat/repeat make new lists too
[123, 'spam', 1.23, 4, 5, 6]
>>> L * 2
[123, 'spam', 1.23, 123, 'spam', 1.23]

>>> L                                  # We're not changing the original list
[123, 'spam', 1.23]

Typspezifische Vorgänge

Die Listen in Python erinnern zwar an Arrays in anderen Sprachen, sind aber in der Regel leistungsfähiger. Zum einen sind sie nicht an einen bestimmten Typ gebunden - die Liste, die wir uns gerade angesehen haben, enthält zum Beispiel drei Objekte völlig unterschiedlicher Typen (eine Ganzzahl, eine Zeichenkette und eine Fließkommazahl). Außerdem haben Listen keine feste Größe. Das heißt, sie können bei Bedarf wachsen und schrumpfen, wenn listenspezifische Operationen durchgeführt werden:

>>> L.append('NI')                     # Growing: add object at end of list
>>> L
[123, 'spam', 1.23, 'NI']

>>> L.pop(2)                           # Shrinking: delete an item in the middle
1.23
>>> L                                  # "del L[2]" deletes from a list too
[123, 'spam', 'NI']

Hier vergrößert die Methode list append die Liste und fügt ein Element am Ende ein; die Methode pop (oder eine entsprechende del Anweisung) entfernt dann ein Element an einer bestimmten Stelle, wodurch die Liste schrumpft. Andere Listenmethoden fügen ein Element an einer beliebigen Position ein (insert), entfernen ein bestimmtes Element nach Wert (remove), fügen mehrere Elemente am Ende hinzu (extend), und so weiter. Da Listen veränderbar sind, ändern die meisten Listenmethoden auch das bestehende Listenobjekt, anstatt ein neues zu erstellen:

>>> M = ['bb', 'aa', 'cc']
>>> M.sort()
>>> M
['aa', 'bb', 'cc']
>>> M.reverse()
>>> M
['cc', 'bb', 'aa']

Die Methode sort ordnet die Liste beispielsweise standardmäßig in aufsteigender Reihenfolge an, und reverse kehrt dies um - in beiden Fällen ändern die Methoden die Liste direkt.

Überprüfung der Grenzen

Obwohl Listen keine feste Größe haben, erlaubt Python es uns nicht, auf Elemente zu verweisen, die nicht vorhanden sind. Das Indizieren am Ende einer Liste ist immer ein Fehler, aber auch das Zuweisen am Ende:

>>> L
[123, 'spam', 'NI']

>>> L[99]
...error text omitted...
IndexError: list index out of range

>>> L[99] = 1
...error text omitted...
IndexError: list assignment index out of range

Das ist Absicht, denn normalerweise ist es ein Fehler, wenn man versucht, das Ende einer Liste zuzuweisen (und ein besonders fieser in der Sprache C, die nicht so viele Fehlerprüfungen wie Python durchführt). Anstatt die Liste stillschweigend zu vergrößern, meldet Python einen Fehler. Um eine Liste zu vergrößern, rufen wir stattdessen Listenmethoden wie appendauf.

Nesting

Eine nette Eigenschaft von Pythons Kerndatentypen ist, dass sie beliebige Verschachtelungenunterstützen - wirkönnen sie in jeder Kombination und so tief wie möglich verschachteln. Wir können zum Beispiel eine Liste haben, die ein Wörterbuch enthält, das wiederum eine Liste enthält, und so weiter. Eine unmittelbare Anwendung dieser Funktion ist die Darstellung von Matrizen oder "mehrdimensionalen Arrays" in Python. Eine Liste mit verschachtelten Listen reicht für einfache Anwendungen aus (in einigen Interfaces erhältst du in den Zeilen 2 und 3 der folgenden Zeilen Eingabeaufforderungen, nicht aber in IDLE):

>>> M = [[1, 2, 3],               # A 3 × 3 matrix, as nested lists
         [4, 5, 6],               # Code can span lines if bracketed
         [7, 8, 9]]
>>> M
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Hier haben wir eine Liste kodiert, die drei andere Listen enthält. Das Ergebnis ist eine 3 × 3Matrix von Zahlen. Auf eine solche Struktur kann auf verschiedene Weise zugegriffen werden:

>>> M[1]                          # Get row 2
[4, 5, 6]

>>> M[1][2]                       # Get row 2, then get item 3 within the row
6

Die erste Operation hier holt die gesamte zweite Zeile und die zweite das dritte Element in dieser Zeile (sie läuft von links nach rechts, wie die früheren String Strip und Split). Die Aneinanderreihung von Indexoperationen führt uns immer tiefer in unsere verschachtelte Objektstruktur.3

Umfassungen

Zusätzlich zu den Operationen der Sequenz und den Listenmethoden gibt es in Python eine fortschrittlichere Operation, die als Listenverstehensausdruck bekannt ist und sich als leistungsfähige Methode zur Verarbeitung von Strukturen wie unserer Matrix erweist. Nehmen wir zum Beispiel an, dass wir die zweite Spalte unserer Beispielmatrix extrahieren müssen. Da die Matrix zeilenweise gespeichert wird, ist es einfach, die Zeilen durch einfaches Indizieren zu erfassen, aber es ist fast genauso einfach, eine Spalte mit einem Listenausdruck zu erfassen:

>>> col2 = [row[1] for row in M]             # Collect the items in column 2
>>> col2
[2, 5, 8]

>>> M                                        # The matrix is unchanged
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Listenverständnisse leiten sich von der Mengenschreibweise ab; sie sind eine Möglichkeit, eine neue Liste zu erstellen, indem man einen Ausdruck auf jedes Element in einer Folge anwendet, und zwar eines nach dem anderen, von links nach rechts. Listenzusammenfassungen werden in eckigen Klammern kodiert (als Hinweis darauf, dass sie eine Liste bilden) und bestehen aus einem Ausdruck und einem Schleifenkonstrukt, die sich einen Variablennamen teilen (row, hier). Das vorangehende Listenverständnis bedeutet im Grunde das, was es sagt: "Gib mir row[1] für jede Zeile der Matrix M, in einer neuen Liste." Das Ergebnis ist eine neue Liste, die Spalte 2 der Matrix enthält.

Listenverständnisse können in der Praxis komplexer sein:

>>> [row[1] + 1 for row in M]                 # Add 1 to each item in column 2
[3, 6, 9]

>>> [row[1] for row in M if row[1] % 2 == 0]  # Filter out odd items
[2, 8]

Die erste Operation hier fügt zum Beispiel 1 zu jedem Element hinzu, wenn es gesammelt wird, und die zweite verwendet eine if Klausel, um ungerade Zahlen aus dem Ergebnis herauszufiltern, indem sie den % modulus Ausdruck (Rest der Division) verwendet. List Comprehensions erstellen neue Ergebnislisten, aber sie können auch dazu verwendet werden, über jedes beliebige iterierbareObjekt zu iterieren - ein Begriff, den wir später in dieser Vorschau näher erläutern werden. In diesem Beispiel verwenden wir Listenverstehensausdrücke, um eine fest kodierte Liste von Koordinaten und eine Zeichenkette zu durchlaufen:

>>> diag = [M[i][i] for i in [0, 1, 2]]      # Collect a diagonal from matrix
>>> diag
[1, 5, 9]

>>> doubles = [c * 2 for c in 'spam']        # Repeat characters in a string
>>> doubles
['ss', 'pp', 'aa', 'mm']

Diese Ausdrücke können auch verwendet werden, um mehrere Werte zu sammeln, solange wir diese Werte in eine verschachtelte Auflistung verpacken. Im Folgenden wird die Verwendung von rangeveranschaulicht - eine eingebaute , die aufeinanderfolgende Ganzzahlen erzeugt und eine umgebende list benötigt, um alle ihre Werte nur in 3.X anzuzeigen (2.X macht eine physische Liste auf einmal):

>>> list(range(4))                           # 0..3 (list() required in 3.X)
[0, 1, 2, 3]
>>> list(range(−6, 7, 2))                    # −6 to +6 by 2 (need list() in 3.X)
[−6, −4, −2, 0, 2, 4, 6]

>>> [[x ** 2, x ** 3] for x in range(4)]     # Multiple values, "if" filters
[[0, 0], [1, 1], [4, 8], [9, 27]]
>>> [[x, x / 2, x * 2] for x in range(−6, 7, 2) if x > 0]
[[2, 1, 4], [4, 2, 8], [6, 3, 12]]

Wie du wahrscheinlich schon gemerkt hast, sind Listenversteher und ihre Verwandten wie die eingebauten Funktionen map und filter zu komplex, um sie in diesem Vorschaukapitel genauer zu behandeln. Mit dieser kurzen Einführung wollen wir vor allem zeigen, dass Python sowohl einfache als auch fortgeschrittene Werkzeuge in seinem Arsenal hat. List Comprehensions sind ein optionales Feature, aber sie sind in der Praxis sehr nützlich und bieten oft einen erheblichen Geschwindigkeitsvorteil. Sie funktionieren außerdem mit jedem Typ, der in Python eine Sequenz ist, sowie mit einigen Typen, die keine sind. Du wirst später in diesem Buch noch viel mehr über sie erfahren.

In den letzten Pythons wurde die Syntax des Verständnisses für andere Aufgaben verallgemeinert: Sie ist heute nicht mehr nur zum Erstellen von Listen gedacht. Zum Beispiel kann ein Verständnis in Klammern eingeschlossen werden, um Generatoren zu erstellen, die bei Bedarf Ergebnisse liefern. Zur Veranschaulichung: Der sum built-in summiert Elemente in einer Folge - in diesem Beispiel werden alle Elemente in den Zeilen unserer Matrix auf Anfrage summiert:

>>> G = (sum(row) for row in M)              # Create a generator of row sums
>>> next(G)                                  # iter(G) not required here
6
>>> next(G)                                  # Run the iteration protocol next()
15
>>> next(G)
24

Die Built-in-Funktion map kann Ähnliches leisten, indem sie auf Anfrage die Ergebnisse der durchlaufenen Elemente einer Funktion einzeln generiert. Wie bei range zwingt die Einbindung in list dazu, in Python 3.X alle Werte zurückzugeben. In Python 2.X wird dies nicht benötigt, da map stattdessen eine Liste der Ergebnisse auf einmal erstellt:

>>> list(map(sum, M))                        # Map sum over items in M
[6, 15, 24]

In Python 2.7 und 3.X kann die comprehension-Syntax auch zum Erstellen von Mengen und Wörterbüchern verwendet werden:

>>> {sum(row) for row in M}                  # Create a set of row sums
{24, 6, 15}

>>> {i : sum(M[i]) for i in range(3)}        # Creates key/value table of row sums
{0: 6, 1: 15, 2: 24}

Tatsächlich können Listen, Mengen, Wörterbücher und Generatoren in 3.X und 2.7 mit Comprehensions erstellt werden:

>>> [ord(x) for x in 'spaam']                # List of character ordinals
[115, 112, 97, 97, 109]
>>> {ord(x) for x in 'spaam'}                # Sets remove duplicates
{112, 97, 115, 109}
>>> {x: ord(x) for x in 'spaam'}             # Dictionary keys are unique
{'p': 112, 'a': 97, 's': 115, 'm': 109}
>>> (ord(x) for x in 'spaam')                # Generator of values
<generator object <genexpr> at 0x000000000254DAB0>

Um Objekte wie Generatoren, Mengen und Wörterbücher zu verstehen, müssen wir jedoch weitergehen.

Wörterbücher

Python-Wörterbücher sind etwas völlig anderes (Monty Python-Referenz beabsichtigt) - sie sind überhaupt keine Sequenzen, sondern werden als Mappings bezeichnet. Mappings sind ebenfalls Sammlungen anderer Objekte, aber sie speichern Objekte nach Schlüssel statt nach relativer Position. Tatsächlich halten Mappings keine verlässliche Reihenfolge von links nach rechts ein; sie ordnen einfach Schlüssel den zugehörigen Werten zu. Dictionaries, der einzige Mapping-Typ in Pythons Kernobjekten, sind auch veränderbar: Wie Listen können sie an Ort und Stelle geändert werden und bei Bedarf wachsen und schrumpfen. Wie Listen sind sie ein flexibles Werkzeug für die Darstellung von Sammlungen, aber ihre sprechenden Schlüssel sind besser geeignet, wenn die Elemente einer Sammlung benannt oder beschriftet sind - zum Beispiel die Felder eines Datenbankeintrags.

Kartierungsarbeiten

Als Literale werden Wörterbücher in geschweiften Klammern kodiert und bestehen aus einer Reihe von "Schlüssel:Wert"-Paaren. Wörterbücher sind immer dann nützlich, wenn wir eine Reihe von Werten mit Schlüsseln verknüpfen müssen, um z. B. die Eigenschaften von etwas zu beschreiben. Betrachten wir zum Beispiel das folgende Wörterbuch mit drei Einträgen (mit den Schlüsseln "food", "quantity" und "color", vielleicht die Details eines hypothetischen Menüpunkts?

>>> D = {'food': 'Spam', 'quantity': 4, 'color': 'pink'}

Wir können dieses Wörterbuch nach Schlüssel indizieren, um die zugehörigen Werte der Schlüssel abzurufen und zu ändern. Für den Wörterbuchindex wird die gleiche Syntax wie für Sequenzen verwendet, aber das Element in den eckigen Klammern ist ein Schlüssel und keine relative Position:

>>> D['food']              # Fetch value of key 'food'
'Spam'

>>> D['quantity'] += 1     # Add 1 to 'quantity' value
>>> D
{'color': 'pink', 'food': 'Spam', 'quantity': 5}

Auch wenn die Form mit geschweiften Klammern verwendet wird, ist es vielleicht üblicher, dass Wörterbücher auf verschiedene Arten aufgebaut werden (es ist selten, dass man alle Daten seines Programms kennt, bevor es läuft). Der folgende Code zum Beispiel beginnt mit einem leeren Wörterbuch und füllt es Schlüssel für Schlüssel auf. Anders als bei Zuweisungen außerhalb der Grenzen von Listen, die verboten sind, werden bei Zuweisungen an neue Wörterbuchschlüssel diese Schlüssel erstellt:

>>> D = {}
>>> D['name'] = 'Bob'      # Create keys by assignment
>>> D['job']  = 'dev'
>>> D['age']  = 40

>>> D
{'age': 40, 'job': 'dev', 'name': 'Bob'}

>>> print(D['name'])
Bob

Hier verwenden wir Wörterbuchschlüssel als Feldnamen in einem Datensatz, der eine Person beschreibt. In anderen Anwendungen können Wörterbücher auch als Ersatz für Suchvorgänge verwendet werden - die Indizierung eines Wörterbuchs nach Schlüssel ist oft der schnellste Weg, eine Suche in Python zu programmieren.

Wie wir später lernen werden, können wir auch Wörterbücher erstellen, indem wir dem dict Typnamen entweder Schlüsselwortargumente (eine spezielle name=value (eine spezielle Syntax in Funktionsaufrufen) oder das Ergebnis des Zusammenfügens von Sequenzen von Schlüsseln und Werten, die wir zur Laufzeit erhalten (z. B. aus Dateien). Die beiden folgenden Beispiele bilden das gleiche Wörterbuch wie das vorherige Beispiel und die äquivalente {} Literalform, wobei das erste Beispiel weniger Tipparbeit erfordert:

>>> bob1 = dict(name='Bob', job='dev', age=40)                      # Keywords
>>> bob1
{'age': 40, 'name': 'Bob', 'job': 'dev'}

>>> bob2 = dict(zip(['name', 'job', 'age'], ['Bob', 'dev', 40]))    # Zipping
>>> bob2
{'job': 'dev', 'name': 'Bob', 'age': 40}

Beachte, dass die Reihenfolge der Wörterbuchschlüssel von links nach rechts durcheinandergewürfelt ist. Mappings sind nicht positionell geordnet. Wenn du also Glück hast, werden sie in einer anderen Reihenfolge zurückgegeben, als du sie eingegeben hast. Die genaue Reihenfolge kann je nach Python variieren, aber du solltest dich nicht darauf verlassen und auch nicht erwarten, dass deine Reihenfolge der in diesem Buch entspricht.

Nesting Revisited

Im vorherigen Beispiel haben wir ein Wörterbuch mit drei Schlüsseln verwendet, um eine hypothetische Person zu beschreiben. Nehmen wir jedoch an, dass die Informationen komplexer sind: Vielleicht müssen wir einen Vor- und einen Nachnamen sowie mehrere Berufsbezeichnungen erfassen. Das führt zu einer weiteren Anwendung von Pythons Objektverschachtelung. Das folgende Wörterbuch, das auf einmal als Literal kodiert ist, erfasst strukturiertere Informationen:

>>> rec = {'name': {'first': 'Bob', 'last': 'Smith'},
           'jobs': ['dev', 'mgr'],
           'age':  40.5}

Hier haben wir wieder ein Wörterbuch mit drei Schlüsseln an der Spitze (Schlüssel "Name", "Aufträge" und "Alter"), aber die Werte sind komplexer geworden: ein verschachteltes Wörterbuch für den Namen, um mehrere Teile zu unterstützen, und eine verschachtelte Liste für die Aufträge, um mehrere Rollen und zukünftige Erweiterungen zu unterstützen. Wir können auf die Komponenten dieser Struktur genauso zugreifen wie bei unserer listenbasierten Matrix, aber dieses Mal sind die meisten Indizes Wörterbuchschlüssel und keine Listenoffsets:

>>> rec['name']                         # 'name' is a nested dictionary
{'last': 'Smith', 'first': 'Bob'}

>>> rec['name']['last']                 # Index the nested dictionary
'Smith'

>>> rec['jobs']                         # 'jobs' is a nested list
['dev', 'mgr']
>>> rec['jobs'][-1]                     # Index the nested list
'mgr'

>>> rec['jobs'].append('janitor')       # Expand Bob's job description in place
>>> rec
{'age': 40.5, 'jobs': ['dev', 'mgr', 'janitor'], 'name': {'last': 'Smith',
'first': 'Bob'}}

Beachte, wie die letzte Operation hier die verschachtelte Auftragsliste erweitert - da die Auftragsliste ein vom Wörterbuch getrennter Teil des Speichers ist, kann sie frei wachsen und schrumpfen (das Layout des Objektspeichers wird später in diesem Buch noch einmal behandelt).

Der eigentliche Grund, warum ich dir dieses Beispiel zeige, ist, um die Flexibilität der Kerndatentypen von Python zu demonstrieren. Wie du siehst, ermöglicht uns die Verschachtelung, komplexe Informationsstrukturen direkt und einfach aufzubauen. Der Aufbau einer ähnlichen Struktur in einer einfachen Sprache wie C wäre mühsam und würde viel mehr Code erfordern: Wir müssten Strukturen und Arrays anlegen und deklarieren, Werte ausfüllen, alles miteinander verknüpfen und so weiter. In Python geht das alles automatisch - wenn wir den Ausdruck ausführen, wird die gesamte verschachtelte Objektstruktur für uns erstellt. Das ist sogar einer der Hauptvorteile von Skriptsprachen wie Python.

Ebenso wichtig ist, dass wir in einer niedrigeren Sprache darauf achten müssten, den gesamten Speicherplatz des Objekts aufzuräumen, wenn wir ihn nicht mehr benötigen. In Python wird der gesamte Speicherplatz, der von der Struktur des Objekts belegt wird, automatisch aufgeräumt, wenn wir den letzten Verweis auf das Objekt verlieren, z. B. indem wir seine Variable einem anderen Objekt zuweisen:

>>> rec = 0                             # Now the object's space is reclaimed

Technisch gesehen verfügt Python über eine Funktion , die als Speicherbereinigung bekannt ist. Sie räumt ungenutzten Speicherplatz auf, während dein Programm läuft, und befreit dich von der Notwendigkeit, solche Details in deinem Code zu verwalten. In Standard-Python (auch bekannt als CPython) wird der Speicherplatz sofort zurückgewonnen, sobald der letzte Verweis auf ein Objekt entfernt wird. Wie das funktioniert, werden wir später in Kapitel 6 untersuchen. Für den Moment reicht es, wenn du weißt, dass du Objekte frei verwenden kannst, ohne dich um die Erstellung ihres Platzes oder das Aufräumen zu kümmern.

Achte auch auf eine Datensatzstruktur, die derjenigen ähnelt, die wir gerade in Kapitel 8, Kapitel 9 und Kapitel 27 programmiert haben. Dort werden wir sie verwenden, um Listen, Wörterbücher, Tupel, benannte Tupel und Klassen zu vergleichen und gegenüberzustellen - eine Reihe von Datenstrukturoptionen mit Kompromissen, die wir später in ausführlich behandeln werden.4

Fehlende Schlüssel: wenn Tests

Als Mappings unterstützen Wörterbücher nur den Zugriff auf Elemente nach Schlüssel, mit der Art von Operationen, die wir gerade gesehen haben. Darüber hinaus unterstützen sie aber auch typspezifische Operationen mit Methodenaufrufen, die in einer Reihe von häufigen Anwendungsfällen nützlich sind. Obwohl wir zum Beispiel einen neuen Schlüssel zuweisen können, um ein Wörterbuch zu erweitern, ist es trotzdem ein Fehler, einen nicht existierenden Schlüssel zu holen:

>>> D = {'a': 1, 'b': 2, 'c': 3}
>>> D
{'a': 1, 'c': 3, 'b': 2}

>>> D['e'] = 99                      # Assigning new keys grows dictionaries
>>> D
{'a': 1, 'c': 3, 'b': 2, 'e': 99}

>>> D['f']                           # Referencing a nonexistent key is an error
...error text omitted...
KeyError: 'f'

Das ist genau das, was wir wollen - normalerweise ist es ein Programmierfehler, etwas zu holen, das nicht wirklich vorhanden ist. Aber in einigen allgemeinen Programmen können wir nicht immer wissen, welche Schlüssel vorhanden sein werden, wenn wir unseren Code schreiben. Wie können wir mit solchen Fällen umgehen und Fehler vermeiden? Eine Lösung ist, im Voraus zu testen. Mit dem Ausdruck dictionary in membership können wir das Vorhandensein eines Schlüssels abfragen und mit einer Python-Anweisung ifauf das Ergebnis verzweigen. Achte im Folgenden darauf, dass du zweimal die Eingabetaste drückst, um die if interaktiv auszuführen, nachdem du ihren Code eingegeben hast (wie in Kapitel 3 erklärt, bedeutet eine leere Zeile an der interaktiven Eingabeaufforderung "go"). Genau wie bei den früheren mehrzeiligen Wörterbüchern und Listen ändert sich die Eingabeaufforderung bei einigen Schnittstellen ab der zweiten Zeile in "...":

>>> 'f' in D
False

>>> if not 'f' in D:                           # Python's sole selection statement
       print('missing')

missing

In diesem Buch wird in späteren Kapiteln mehr über die if Anweisung gesagt, aber die Form, die wir hier verwenden, ist einfach: Sie besteht aus dem Wort if, gefolgt von einem Ausdruck, der als wahres oder falsches Ergebnis interpretiert wird, gefolgt von einem Codeblock, der ausgeführt wird, wenn der Test wahr ist. In ihrer vollständigen Form kann die if Anweisung auch eine else Klausel für einen Standardfall und eine oder mehrere elif ("else if") Klauseln für andere Tests enthalten. Zusammen mit dem ternären if/elseAusdruck (den wir gleich kennenlernen werden) und dem if Verstehensfilter, den wir bereits gesehen haben, ist sie das wichtigste Werkzeug in Python, um dieLogik von Entscheidungen in unseren Skripten zu kodieren.

Wenn du in der Vergangenheit andere Programmiersprachen verwendet hast, fragst du dich vielleicht, wie Python weiß, wann die Anweisung if endet. Ich werde die Syntaxregeln von Python in späteren Kapiteln näher erläutern, aber kurz gesagt: Wenn du mehr als eine Aktion in einem Anweisungsblock auszuführen hast, räumst du einfach alle Anweisungen auf die gleiche Weise ein - das fördert die Lesbarkeit des Codes und reduziert die Anzahl der Zeichen, die du eingeben musst:

>>> if not 'f' in D:
        print('missing')
        print('no, really...')                 # Statement blocks are indented

missing
no, really...

Neben dem in Test gibt es eine Reihe von Möglichkeiten, den Zugriff auf nicht existierende Schlüssel in den von uns erstellten Wörterbüchern zu vermeiden: die getMethode, ein bedingter Index mit einem Standardwert; die Python 2.X has_keyMethode, ein in ähnliches Werkzeug, das in 3.X nicht mehr verfügbar ist; die tryAnweisung, ein Werkzeug, das wir zum ersten Mal in Kapitel 10 kennenlernen, das Ausnahmen abfängt und wiederherstellt; und der if/elseternäre (dreiteilige) Ausdruck, der im Wesentlichen eine if Anweisung ist, die auf eine einzige Zeile gequetscht wird. Hier sind ein paar Beispiele:

>>> value = D.get('x', 0)                      # Index but with a default
>>> value
0
>>> value = D['x'] if 'x' in D else 0          # if/else expression form
>>> value
0

Die Details zu solchen Alternativen heben wir uns für ein späteres Kapitel auf. Wenden wir uns jetzt einer anderen Wörterbuchmethode zu, die in einem gängigen Anwendungsfall eine Rolle spielt.

Sortierschlüssel: für Schleifen

Wie bereits erwähnt , halten Wörterbücher keine verlässliche Reihenfolge von links nach rechts ein, da sie keine Sequenzen sind. Wenn wir ein Wörterbuch erstellen und es ausdrucken, können die Schlüssel in einer anderen Reihenfolge zurückkommen, als wir sie eingegeben haben, und sie können je nach Python-Version und anderen Variablen variieren:

>>> D = {'a': 1, 'b': 2, 'c': 3}
>>> D
{'a': 1, 'c': 3, 'b': 2}

Was tun wir aber, wenn wir die Elemente eines Wörterbuchs in eine bestimmte Reihenfolge bringen müssen? Eine gängige Lösung ist es, eine Liste von Schlüsseln mit der Methode dictionary keys zu erfassen, diese mit der Methode list sort zu sortieren und dann das Ergebnis mit einer Python for Schleife durchzugehen (wie bei if musst du nach dem Programmieren der folgenden forSchleife zweimal die Eingabetaste drücken und in Python 2.X die äußere Klammer in print weglassen):

>>> Ks = list(D.keys())                # Unordered keys list
>>> Ks                                 # A list in 2.X, "view" in 3.X: use list()
['a', 'c', 'b']

>>> Ks.sort()                          # Sorted keys list
>>> Ks
['a', 'b', 'c']

>>> for key in Ks:                     # Iterate though sorted keys
        print(key, '=>', D[key])       # <== press Enter twice here (3.X print)

a => 1
b => 2
c => 3

Dies ist ein dreistufiger Prozess, obwohl er, wie wir in späteren Kapiteln sehen werden, in neueren Versionen von Python mit der eingebauten Funktion neuer sorted in einem Schritt erledigt werden kann. Der Aufruf von sorted gibt das Ergebnis zurück und sortiert eine Vielzahl von Objekttypen, in diesem Fall die Wörterbuchschlüssel automatisch:

>>> D
{'a': 1, 'c': 3, 'b': 2}

>>> for key in sorted(D):
        print(key, '=>', D[key])

a => 1
b => 2
c => 3

Neben der Vorstellung von Wörterbüchern dient dieser Anwendungsfall auch dazu, die Python-Schleife for vorzustellen. Die for Schleife ist eine einfache und effiziente Methode, um alle Elemente einer Sequenz zu durchlaufen und einen Codeblock für jedes Element auszuführen. Eine benutzerdefinierte Schleifenvariable (hierkey) wird verwendet, um das aktuelle Element bei jedem Durchlauf zu referenzieren. Das Ergebnis in unserem Beispiel ist, dass die Schlüssel und Werte des ungeordneten Wörterbuchs in der Reihenfolge der Schlüssel gedruckt werden.

Die for Schleife und ihr allgemeinerer Kollege , die while Schleife, sind die wichtigsten Methoden, mit denen wir sich wiederholende Aufgaben als Anweisungen in unseren Skripten codieren. In Wirklichkeit ist die forSchleife, wie ihr Verwandter, das Listenverständnis, eine Sequenzoperation. Sie funktioniert mit jedem Objekt, das eine Sequenz ist, und, wie das Listenverständnis, auch mit einigen Objekten, die keine sind. In diesem Beispiel durchläuft sie die Zeichen einer Zeichenkette und druckt jedes Zeichen in Großbuchstaben aus, während sie es durchläuft:

>>> for c in 'spam':
        print(c.upper())

S
P
A
M

Die while Schleife von Python ist eine allgemeinere Art von Schleife; sie ist nicht auf das Durchlaufen von Sequenzen beschränkt, erfordert aber im Allgemeinen mehr Code, um dies zu tun:

>>> x = 4
>>> while x > 0:
        print('spam!' * x)
        x -= 1

spam!spam!spam!spam!
spam!spam!spam!
spam!spam!
spam!

Wir werden die Schleifenanweisungen, die Syntax und die Werkzeuge später in diesem Buch ausführlich besprechen. Zunächst muss ich jedoch gestehen, dass dieser Abschnitt nicht so ausführlich war, wie er hätte sein können. Die Schleife for und alle anderen Schleifen, die Objekte von links nach rechts durchlaufen, sind nicht einfach nur Sequenzoperationen, sondern iterierbare Operationen - wie im nächsten Abschnitt beschrieben.

Iteration und Optimierung

Wenn die Schleife for des letzten Abschnitts wie der zuvor vorgestellte Ausdruck für das Listenverständnis aussieht, dann sollte sie das auch: Beide sind wirklich allgemeine Iterationswerkzeuge. Tatsächlich funktionieren beide mit jedem iterierbaren Objekt, das dem Iterationsprotokoll folgt - einer grundlegenden Idee in Python, die allen Iterationswerkzeugen zugrunde liegt.

Kurz gesagt ist ein Objekt iterierbar, wenn es entweder eine physisch gespeicherte Sequenz im Speicher ist oder ein Objekt, das im Rahmen einer Iterationsoperation ein Element nach dem anderen erzeugt - eine Art "virtuelle" Sequenz. Formal gesehen gelten beide Arten von Objekten als iterierbar, weil sie das Iterationsprotokollunterstützen : antwortet auf den Aufruf itermit einem Objekt, das in auf next Aufrufe reagiert und eine Ausnahme auslöst, wenn es keine Werte mehr erzeugt.

Der Generator-Verständnisausdruck , den wir vorhin gesehen haben, ist ein solches Objekt: Seine Werte werden nicht auf einmal im Speicher gespeichert, sondern nach Bedarf erzeugt, normalerweise von Iterationswerkzeugen. Python-Datei-Objektewerden ebenfalls Zeile für Zeile iteriert, wenn sie von einem Iterationswerkzeug verwendet werden: Der Dateiinhalt wird nicht in einer Liste gespeichert, sondern bei Bedarf abgerufen. Beides sind iterierbare Objekte in Python - eine Kategorie, die sich in 3.X um Kernwerkzeuge wie range und map erweitert. Indem sie die Ergebnisse nach Bedarf verschieben, können diese Werkzeuge sowohl Speicherplatz sparen als auch Verzögerungen minimieren.

Ich werde später in diesem Buch mehr über das Iterationsprotokoll sagen. Denke daran, dass jedes Python-Tool, das ein Objekt von links nach rechts durchsucht, das Iterationsprotokoll verwendet. Aus diesem Grund funktioniert der Aufruf sorted aus dem vorherigen Abschnitt direkt mit dem Wörterbuch - wir müssen nicht die Methode keys aufrufen, um eine Sequenz zu erhalten, weil Wörterbücher iterierbare Objekte sind, mit einer next, die aufeinanderfolgende Schlüssel zurückgibt.

Vielleicht hilft es dir auch, zu sehen, dass jeder Listenverständnis-Ausdruck, wie dieser hier, der die Quadrate einer Liste von Zahlen berechnet:

>>> squares = [x ** 2 for x in [1, 2, 3, 4, 5]]
>>> squares
[1, 4, 9, 16, 25]

kann immer als äquivalente for Schleife kodiert werden, die die Ergebnisliste manuell aufbaut, indem sie nach und nach angehängt wird:

>>> squares = []
>>> for x in [1, 2, 3, 4, 5]:          # This is what a list comprehension does
        squares.append(x ** 2)         # Both run the iteration protocol internally

>>> squares
[1, 4, 9, 16, 25]

Beide Werkzeuge nutzen intern das Iterationsprotokoll und erzielen das gleiche Ergebnis. Das Listenverständnis und verwandte funktionale Programmierwerkzeuge wie map und filter laufen jedoch bei einigen Arten von Code oft schneller als eine for Schleife (vielleicht sogar doppelt so schnell) - eine Eigenschaft, die bei deinen Programmen für große Datensätze wichtig sein könnte. Allerdings sollte ich darauf hinweisen, dass Leistungsmessungen in Python eine heikle Angelegenheit sind, weil Python so viel optimiert, und dass sie von Version zu Version variieren können.

Eine wichtige Faustregel in Python ist, dass du zuerst für Einfachheit und Lesbarkeit programmieren solltest und dich erst dann um die Leistung kümmerst, wenn dein Programm funktioniert und du bewiesen hast, dass es wirklich ein Leistungsproblem gibt. In den meisten Fällen wird dein Code auch so schon schnell genug sein. Wenn du den Code dennoch für die Leistung optimieren musst, enthält Python Tools, die dir dabei helfen: mit den Modulen time und timeit, um die Geschwindigkeit von Alternativen zu messen, und das Modul profile, um Engpässe zu isolieren.

Du wirst später in diesem Buch (insbesondere in der Benchmarking-Fallstudie in Kapitel 21) und in den Python-Handbüchern mehr darüber erfahren. Für diese Vorschau gehen wir nun zum nächsten zentralen Datentyp über.

Tupel

Das Tupel-Objekt (ausgesprochen "toople" oder "tuhple", je nachdem, wen du fragst) ist in etwa wie eine Liste, die nicht geändert werden kann - Tupel sind Sequenzen, wie Listen, aber sie sind unveränderlich, wie Zeichenketten. Funktional werden sie verwendet, um feste Sammlungen von Elementen darzustellen: die Komponenten eines bestimmten Kalenderdatums zum Beispiel. Syntaktisch werden sie normalerweise in Klammern statt in eckigen Klammern kodiert und unterstützen beliebige Typen, beliebige Verschachtelungen und die üblichen Sequenzoperationen:

>>> T = (1, 2, 3, 4)            # A 4-item tuple
>>> len(T)                      # Length
4

>> T + (5, 6)                   # Concatenation
(1, 2, 3, 4, 5, 6)

>>> T[0]                        # Indexing, slicing, and more
1

Tupel haben ab Python 2.6 und 3.0 auch typspezifische aufrufbare Methoden, aber nicht annähernd so viele wie Listen:

>>> T.index(4)                  # Tuple methods: 4 appears at offset 3
3
>>> T.count(4)                  # 4 appears once
1

Der wichtigste Unterschied bei Tupeln ist, dass sie nicht geändert werden können, sobald sie erstellt wurden. Das heißt, sie sind unveränderliche Sequenzen (Ein-Element-Tupel wie das hier erfordern ein Komma am Ende):

>>> T[0] = 2                    # Tuples are immutable
...error text omitted...
TypeError: 'tuple' object does not support item assignment

>>> T = (2,) + T[1:]            # Make a new tuple for a new value
>>> T
(2, 2, 3, 4)

Wie Listen und Wörterbücher unterstützen Tupel gemischte Typen und Verschachtelungen, aber sie wachsen und schrumpfen nicht, weil sie unveränderlich sind (die Klammern, die die Elemente eines Tupels einschließen, können oft weggelassen werden, wie hier geschehen; in Kontexten, in denen Kommas sonst keine Rolle spielen, sind die Kommas das, was ein Tupel ausmacht):

>>> T = 'spam', 3.0, [11, 22, 33]
>>> T[1]
3.0
>>> T[2][1]
22
>>> T.append(4)
AttributeError: 'tuple' object has no attribute 'append'

Warum Tupel?

Warum also einen Typ haben, der wie eine Liste ist, aber weniger Operationen unterstützt? Ehrlich gesagt werden Tupel in der Praxis nicht so oft verwendet wie Listen, aber ihre Unveränderlichkeit ist der springende Punkt. Wenn du eine Sammlung von Objekten als Liste in deinem Programm weitergibst, kann sie überall geändert werden; wenn du ein Tupel verwendest, kann sie das nicht. Das heißt, Tupel bieten eine Art Integritätsbeschränkung, die in Programmen, die größer sind als die, die wir hier schreiben werden, praktisch ist. Im weiteren Verlauf des Buches werden wir mehr über Tupel erfahren, einschließlich einer Erweiterung, die auf ihnen aufbaut , den sogenannten benannten Tupeln. Kommen wir aber erst einmal zu unserem letzten wichtigen Kerntyp: der Datei.

Dateien

Dateiobjekte sind die wichtigste Schnittstelle des Python-Codes zu externen Dateien auf deinem Computer. Mit ihnen kannst du Textmemos, Audioclips, Excel-Dokumente, gespeicherte E-Mails und alles andere, was du auf deinem Computer gespeichert hast, lesen und schreiben. Dateien sind ein zentraler Typ, aber sie sind etwas ungewöhnlich - es gibt keine spezielle Syntax, um sie zu erstellen. Um ein Datei-Objekt zu erstellen, rufst du die integrierte Funktion open auf und übergibst einen externen Dateinamen und einen optionalen Verarbeitungsmodus als Zeichenkette.

Um zum Beispiel eine Textausgabedatei zu erstellen, würdest du ihren Namen und die Zeichenfolge 'w' Verarbeitungsmodus zum Schreiben von Daten übergeben:

>>> f = open('data.txt', 'w')      # Make a new file in output mode ('w' is write)
>>> f.write('Hello\n')             # Write strings of characters to it
6
>>> f.write('world\n')             # Return number of items written in Python 3.X
6
>>> f.close()                      # Close to flush output buffers to disk

Dadurch wird eine Datei im aktuellen Verzeichnis erstellt und Text in sie geschrieben (der Dateiname kann ein vollständiger Verzeichnispfad sein, wenn du auf eine Datei an anderer Stelle auf deinem Computer zugreifen musst). Um zurückzulesen, was du gerade geschrieben hast, öffnest du die Datei erneut im Verarbeitungsmodus 'r', um Texteingaben zu lesen - das ist die Standardeinstellung, wenn du den Modus im Aufruf nicht angibst. Lies dann den Inhalt der Datei in eine Zeichenkette ein und zeige sie an. Der Inhalt einer Datei ist in deinem Skript immer ein String, unabhängig davon, welche Art von Daten die Datei enthält:

>>> f = open('data.txt')           # 'r' (read) is the default processing mode
>>> text = f.read()                # Read entire file into a string
>>> text
'Hello\nworld\n'

>>> print(text)                    # print interprets control characters
Hello
world

>>> text.split()                   # File content is always a string
['Hello', 'world']

Andere Datei-Objekt-Methoden unterstützen zusätzliche Funktionen, auf die wir hier nicht eingehen können. Datei-Objekte bieten zum Beispiel mehr Möglichkeiten zum Lesen und Schreiben (read akzeptiert eine optionale maximale Byte-/Zeichengröße, readline liest eine Zeile nach der anderen und so weiter) sowie andere Werkzeuge (seek bewegt sich zu einer neuen Dateiposition). Wie wir später sehen werden, ist der beste Weg, eine Datei zu lesen, sie überhaupt nicht zu lesen - Dateienbieten einen Iterator, der in for Schleifen und anderen Kontexten automatisch Zeile für Zeile liest:

>>> for line in open('data.txt'): print(line)

Wir werden später in diesem Buch alle Dateimethoden kennenlernen. Wenn du aber schon jetzt eine kurze Vorschau sehen willst, rufe dirfür eine beliebige geöffnete Datei auf und wähle help für jeden der zurückgegebenen Methodennamen:

>>> dir(f)
[ ...many names omitted...
'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush',
'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable',
'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable',
'write', 'writelines']

>>>help(f.seek)
...try it and see...

Binäre Bytes Dateien

Die Beispiele aus dem vorherigen Abschnitt veranschaulichen die Grundlagen von Dateien, die für viele Aufgaben ausreichen. Technisch gesehen beruhen sie jedoch entweder auf der Standard-Unicode-Kodierung der Plattform in Python 3.X oder auf der 8-Bit-Byte-Natur von Dateien in Python 2.X. Textdateien kodieren in 3.X immer Strings und schreiben in 2.X blind String-Inhalte. Bei umfangreicheren Datentypen können die Dateischnittstellen jedoch sowohl vom Inhalt als auch von der verwendeten Python-Zeile abhängen.

Wie bereits bei der Begegnung mit Strings angedeutet, unterscheidet Python 3.X scharf zwischen Text- und Binärdaten in Dateien: Textdateien stellen den Inhalt als normale str Strings dar und führen beim Schreiben und Lesen von Daten automatisch eine Unicode-Kodierung und -Dekodierung durch, während Binärdateien den Inhalt als speziellen bytes String darstellen und den Zugriff auf den Dateiinhalt unverändert ermöglichen. Python 2.X unterstützt dieselbe Zweiteilung, setzt sie aber nicht so starr um und die Werkzeuge unterscheiden sich.

Binärdateien sind zum Beispiel nützlich für die Verarbeitung von Medien, den Zugriff auf Daten, die von C-Programmen erstellt wurden, und so weiter. Zur Veranschaulichung: Das Python-Modul struct kann gepackte Binärdaten - roheBytes, die Werte aufzeichnen, die keine Python-Objekte sind - erstellen und entpacken, um sie im Binärmodus in eine Datei zu schreiben. Wir werden uns mit dieser Technik später im Buch im Detail befassen, aber das Konzept ist einfach: Das Folgende erstellt eine Binärdatei in Python 3.X (Binärdateien funktionieren in 2.X genauso, aber das Präfix "b" für das String-Literal ist nicht erforderlich und wird nicht angezeigt):

>>> import struct
>>> packed = struct.pack('>i4sh', 7, b'spam', 8)     # Create packed binary data
>>> packed                                           # 10 bytes, not objects or text
b'\x00\x00\x00\x07spam\x00\x08'
>>>
>>> file = open('data.bin', 'wb')                    # Open binary output file
>>> file.write(packed)                               # Write packed binary data
10
>>> file.close()

Das Zurücklesen von Binärdaten ist im Wesentlichen symmetrisch; nicht alle Programme müssen so tief in die Low-Level-Welt der Bytes vordringen, aber Binärdateien machen dies in Python einfach:

>>> data = open('data.bin', 'rb').read()              # Open/read binary data file
>>> data                                              # 10 bytes, unaltered
b'\x00\x00\x00\x07spam\x00\x08'
>>> data[4:8]                                         # Slice bytes in the middle
b'spam'
>>> list(data)                                        # A sequence of 8-bit bytes
[0, 0, 0, 7, 115, 112, 97, 109, 0, 8]
>>> struct.unpack('>i4sh', data)                      # Unpack into objects again
(7, b'spam', 8)

Unicode-Textdateien

Textdateien werden verwendet, um alle Arten von textbasierten Daten zu verarbeiten, von Notizen über E-Mail-Inhalte bis hin zu JSON- und XML-Dokumenten. In der heutigen vernetzten Welt können wir jedoch nicht mehr von Text sprechen, ohne zu fragen: "Welcher Art?" - du musst auch den Unicode-Kodierungstyp des Textes kennen, wenn er sich entweder von der Standardeinstellung deiner Plattform unterscheidet oder du dich aus Gründen der Datenportabilität nicht auf diese Einstellung verlassen kannst.

Zum Glück ist das einfacher, als es sich vielleicht anhört. Um auf Dateien zuzugreifen, die Nicht-ASCII-Unicode-Text enthalten, wie er weiter oben in diesem Kapitel vorgestellt wurde, geben wir einfach einen Kodierungsnamen an, wenn der Text in der Datei nicht der Standardkodierung für unsere Plattform entspricht. In diesem Modus werden Python-Textdateien beim Schreiben automatisch kodiert und beim Lesen dekodiert, je nachdem, welches Kodierungsschema du angibst. In Python 3 .X:

>>> S = 'sp\xc4m'                                          # Non-ASCII Unicode text
>>> S
'spÄm'
>>> S[2]                                                   # Sequence of characters
'Ä'

>>> file = open('unidata.txt', 'w', encoding='utf-8')      # Write/encode UTF-8 text
>>> file.write(S)                                          # 4 characters written
4
>>> file.close()

>>> text = open('unidata.txt', encoding='utf-8').read()    # Read/decode UTF-8 text
>>> text
'spÄm'
>>> len(text)                                              # 4 chars (code points)
4

Diese automatische Kodierung und Dekodierung ist das, was du normalerweise willst. Da Dateien dies bei der Übertragung übernehmen, kannst du Text im Speicher als einfache Zeichenkette verarbeiten, ohne dich um seine Unicode-kodierte Herkunft zu kümmern. Bei Bedarf kannst du aber auch sehen, was wirklich in deiner Datei gespeichert ist, indem du in den Binärmodus wechselst:

>>> raw = open('unidata.txt', 'rb').read()                 # Read raw encoded bytes
>>> raw
b'sp\xc3\x84m'
>>> len(raw)                                               # Really 5 bytes in UTF-8
5

Du kannst auch manuell kodieren und dekodieren, wenn du Unicode-Daten aus einer anderen Quelle als einer Datei erhältst, z. B. aus einer geparsten E-Mail oder über eine Netzwerkverbindung:

>>> text.encode('utf-8')                                   # Manual encode to bytes
b'sp\xc3\x84m'
>>> raw.decode('utf-8')                                    # Manual decode to str
'spÄm'

Das ist auch nützlich, um zu sehen, wie Textdateien dieselbe Zeichenkette unter verschiedenen Kodierungsnamen automatisch unterschiedlich kodieren würden, und bietet eine Möglichkeit, Daten in verschiedene Kodierungen zu übersetzen - es sind verschiedene Bytes in den Dateien, aber sie werden im Speicher in dieselbe Zeichenkette dekodiert, wenn du den richtigen Kodierungsnamen angibst:

>>> text.encode('latin-1')                                 # Bytes differ in others
b'sp\xc4m'
>>> text.encode('utf-16')
b'\xff\xfes\x00p\x00\xc4\x00m\x00'

>>> len(text.encode('latin-1')), len(text.encode('utf-16'))
(4, 10)

>>> b'\xff\xfes\x00p\x00\xc4\x00m\x00'.decode('utf-16')    # But same string decoded
'spÄm'

In Python 2.X funktioniert das alles mehr oder weniger gleich, aber Unicode-Strings werden mit einem führenden "u" kodiert und angezeigt, Byte-Strings benötigen kein führendes "b" und Unicode-Textdateien müssen mit codecs.open geöffnet werden, das einen Kodierungsnamen akzeptiert, genau wie 3.X open akzeptiert und die spezielle Zeichenkette unicode verwendet, um den Inhalt im Speicher darzustellen. Der binäre Dateimodus scheint in 2.X optional zu sein, da normale Dateien nur bytebasierte Daten sind, aber er ist erforderlich, um das Ändern von Zeilenenden zu vermeiden, falls vorhanden (mehr dazu später im Buch):

>>> import codecs
>>> codecs.open('unidata.txt', encoding='utf8').read()     # 2.X: read/decode text
u'sp\xc4m'
>>> open('unidata.txt', 'rb').read()                       # 2.X: read raw bytes
'sp\xc3\x84m'
>>> open('unidata.txt').read()                             # 2.X: raw/undecoded too
'sp\xc3\x84m'

Obwohl du dich in der Regel nicht um diese Unterscheidung kümmern musst, wenn du nur mit ASCII-Text arbeitest, sind Pythons Strings und Dateien ein Vorteil, wenn du entweder mit binären Daten (zu denen die meisten Medientypen gehören) oder mit Text in internationalisierten Zeichensätzen arbeitest (zu denen die meisten Inhalte im Web und im Internet insgesamt gehören). Python unterstützt auch Nicht-ASCII-Dateinamen (nicht nur Inhalte), aber das geschieht weitgehend automatisch. Werkzeuge wie Walker und Lister bieten bei Bedarf mehr Kontrolle, aber wir verschieben weitere Details von auf Kapitel 37.

Andere dateiähnliche Werkzeuge

Die Funktion open ist das Arbeitspferd für die meisten Dateiverarbeitungen, die du in Python durchführen wirst. Für fortgeschrittene Aufgaben bietet Python jedoch zusätzliche dateiähnliche Werkzeuge: Pipes, FIFOs, Sockets, Dateien mit Schlüsselzugriff, persistente Objektregale, deskriptorbasierte Dateien, relationale und objektorientierte Datenbankschnittstellen und mehr. Deskriptordateien unterstützen zum Beispiel Dateisperren und andere Low-Level-Tools, und Sockets bieten eine Schnittstelle für die Netzwerk- und Interprozesskommunikation. Viele dieser Themen werden wir in diesem Buch nicht behandeln, aber du wirst sie nützlich finden, sobald du ernsthaft mit der Programmierung von Python beginnst.

Andere Kerntypen

Neben den Kerntypen, die wir bisher kennengelernt haben, gibt es noch weitere, die je nach Definition zu dieser Kategorie gehören können oder auch nicht. Mengen zum Beispiel sind eine neuere Ergänzung der Sprache, die weder Zuordnungen noch Sequenzen sind; sie sind vielmehr ungeordnete Sammlungen eindeutiger und unveränderlicher Objekte. Du erstellst Mengen, indem du die eingebaute Funktion set aufrufst oder die neuen Mengenliterale und Ausdrücke in 3.X und 2.7 verwendest. Sie unterstützen die üblichen mathematischen Mengenoperationen (die Wahl der neuen {...} Syntax für Mengenliterale ist sinnvoll, da Mengen den Schlüsseln eines wertfreien Wörterbuchs ähneln):

>>> X = set('spam')                 # Make a set out of a sequence in 2.X and 3.X
>>> Y = {'h', 'a', 'm'}             # Make a set with set literals in 3.X and 2.7

>>> X, Y                            # A tuple of two sets without parentheses
({'m', 'a', 'p', 's'}, {'m', 'a', 'h'})

>>> X & Y                           # Intersection
{'m', 'a'}
>>> X | Y                           # Union
{'m', 'h', 'a', 'p', 's'}
>>> X - Y                           # Difference
{'p', 's'}
>>> X > Y                           # Superset
False

>>> {n ** 2 for n in [1, 2, 3, 4]}  # Set comprehensions in 3.X and 2.7
{16, 1, 4, 9}

Auch weniger mathematisch begabte Programmierer finden Sets oft nützlich für gängige Aufgaben wie das Herausfiltern von Duplikaten, das Isolieren von Unterschieden und das Durchführen von ordnungsneutralen Gleichheitstests, ohne Listen, Strings und alle anderen iterierbaren Objekte zu sortieren:

>>> list(set([1, 2, 1, 3, 1]))      # Filtering out duplicates (possibly reordered)
[1, 2, 3]
>>> set('spam') - set('ham')        # Finding differences in collections
{'p', 's'}
>>> set('spam') == set('asmp')      # Order-neutral equality ('spam'=='asmp' False)
True

Sets unterstützen auch in Mitgliedschaftstests, obwohl alle anderen Sammlungstypen in Python dies auch tun:

>>> 'p' in set('spam'), 'p' in 'spam', 'ham' in ['eggs', 'spam', 'ham']
(True, True, True)

Außerdem hat Python vor kurzem ein paar neue Zahlentypen bekommen: Dezimalzahlen, die Gleitkommazahlen mit fester Genauigkeit sind, und Bruchzahlen, die rationale Zahlen mit einem Zähler und einem Nenner sind. Beide können verwendet werden, um die Einschränkungen und Ungenauigkeiten der Gleitkommamathematik zu umgehen:

>>> 1 / 3                           # Floating-point (add a .0 in Python 2.X)
0.3333333333333333
>>> (2/3) + (1/2)
1.1666666666666665

>>> import decimal                  # Decimals: fixed precision
>>> d = decimal.Decimal('3.141')
>>> d + 1
Decimal('4.141')

>>> decimal.getcontext().prec = 2
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.33')

>>> from fractions import Fraction  # Fractions: numerator+denominator
>>> f = Fraction(2, 3)
>>> f + 1
Fraction(5, 3)
>>> f + Fraction(1, 2)
Fraction(7, 6)

Python kommt auch mit Booleschen Werten (mit vordefinierten True und False Objekten, die im Wesentlichen nur die ganzen Zahlen 1 und 0 mit einer eigenen Anzeigelogik sind) und unterstützt seit langem ein spezielles Platzhalterobjekt namens None, das häufig zur Initialisierung von Namen und Objekten verwendet wird:

>>> 1 > 2, 1 < 2                    # Booleans
(False, True)
>>> bool('spam')                    # Object's Boolean value
True

>>> X = None                        # None placeholder
>>> print(X)
None
>>> L = [None] * 100                # Initialize a list of 100 Nones
>>> L
[None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, ...a list of 100 Nones...]

Wie du die Flexibilität deines Codes brechen kannst

Ich werde später mehr über alle Objekttypen von Python sagen, aber einer verdient hier eine besondere Behandlung. Der Objekttyp, der von der eingebauten Funktion type zurückgegeben wird, ist ein Objekt, das den Typ eines anderen Objekts angibt. Das Ergebnis unterscheidet sich in 3.X leicht, weil Typen und Klassen vollständig miteinander verschmolzen sind (etwas, das wir im Zusammenhang mit den "new-style" Klassen in Teil VI untersuchen werden). Angenommen, L ist immer noch die Liste aus dem vorherigen Abschnitt:

# In Python 2.X:
>>> type(L)                         # Types: type of L is list type object
<type 'list'>
>>> type(type(L))                   # Even types are objects
<type 'type'>

# In Python 3.X:
>>> type(L)                         # 3.X: types are classes, and vice versa
<class 'list'>
>>> type(type(L))                   # See Chapter 32 for more on class types
<class 'type'>

Neben der Möglichkeit, deine Objekte interaktiv zu erkunden, ermöglicht das type Objekt in seiner praktischsten Anwendung, dass der Code die Typen der Objekte, die er verarbeitet, überprüfen kann. Tatsächlich gibt es mindestens drei Möglichkeiten, dies in einem Python-Skript zu tun:

>>> if type(L) == type([]):         # Type testing, if you must...
        print('yes')

yes
>>> if type(L) == list:             # Using the type name
        print('yes')

yes
>>> if isinstance(L, list):         # Object-oriented tests
        print('yes')

yes

Jetzt, wo ich dir all diese Möglichkeiten der Typprüfung gezeigt habe, bin ich jedoch gesetzlich verpflichtet, dir zu sagen, dass dies in einem Python-Programm fast immer das Falsche ist (und oft ein Zeichen dafür ist, dass ein ehemaliger C-Programmierer zum ersten Mal mit Python arbeitet! Der Grund dafür wird erst später im Buch klar, wenn wir anfangen, größere Codeeinheiten wie Funktionen zu schreiben, aber es ist ein (vielleicht das) Kernkonzept von Python. Wenn du deinen Code auf bestimmte Typen hin prüfst, unterbrichst du seine Flexibilität - du schränkst ihn darauf ein, nur mit einem Typ zu arbeiten. Ohne solche Tests kann dein Code mit einer ganzen Reihe von Typen arbeiten.

Das hängt mit der bereits erwähnten Idee des Polymorphismus zusammen und rührt daher, dass es in Python keine Typendeklarationen gibt. Wie du noch lernen wirst, codieren wir in Python für Objektschnittstellen(unterstützte Operationen) und nicht für Typen. Das heißt, wir kümmern uns darum, was ein Objekt tut, nicht was es ist. Wenn wir uns nicht um bestimmte Typen kümmern, bedeutet das, dass der Code automatisch auf viele von ihnen anwendbar ist - jedes Objekt mit einer kompatiblen Schnittstelle wird funktionieren, unabhängig von seinem spezifischen Typ. Obwohl Typprüfungen unterstützt werden - und in einigen seltenen Fällen sogar erforderlich sind - wirst du sehen, dass sie in der Regel nicht der "pythonischen" Denkweise entsprechen. Tatsächlich wirst du feststellen, dass Polymorphismus wahrscheinlich die Schlüsselidee ist, um Python gut zu nutzen.

Benutzerdefinierte Klassen

Wir werden uns später in diesem Buch eingehend mit der objektorientierten Programmierung in Python befassen - einer optionalen, aber leistungsstarken Funktion der Sprache, die die Entwicklungszeit verkürzt, indem sie die Programmierung durch Anpassungen unterstützt. Abstrakt betrachtet definieren Klassen jedoch neue Objekttypen, die den Kernsatz erweitern, sodass sie hier nur einen kurzen Blick wert sind. Nehmen wir zum Beispiel an, du möchtest einen Objekttyp haben, der Mitarbeiter modelliert. Auch wenn es in Python keinen solchen speziellen Kerntyp gibt, könnte die folgende benutzerdefinierte Klasse dafür geeignet sein:

>>> class Worker:
         def __init__(self, name, pay):          # Initialize when created
             self.name = name                    # self is the new object
             self.pay  = pay
         def lastName(self):
             return self.name.split()[-1]        # Split string on blanks
         def giveRaise(self, percent):
             self.pay *= (1.0 + percent)         # Update pay in place

Diese Klasse definiert eine neue Art von Objekt, das die Attribute name und pay (manchmal auch genannt ) sowie zwei Verhaltensweisen hat, die als Funktionen kodiert sind (normalerweise Methoden genannt). Wenn du die Klasse wie eine Funktion aufrufst, werden Instanzen unseres neuen Typs erzeugt, und die Methoden der Klasse erhalten automatisch die Instanz, die von einem bestimmten Methodenaufruf verarbeitet wird (im Argument self ):

>>> bob = Worker('Bob Smith', 50000)             # Make two instances
>>> sue = Worker('Sue Jones', 60000)             # Each has name and pay attrs
>>> bob.lastName()                               # Call method: bob is self
'Smith'
>>> sue.lastName()                               # sue is the self subject
'Jones'
>>> sue.giveRaise(.10)                           # Updates sue's pay
>>> sue.pay
66000.0

Das implizierte "Selbst"-Objekt ist der Grund, warum wir dies ein objektorientiertes Modell nennen: Es gibt immer ein impliziertes Subjekt in den Funktionen innerhalb einer Klasse. In gewissem Sinne baut der klassenbasierte Typ jedoch einfach auf den Kerntypen auf und verwendet diese - ein benutzerdefiniertes Worker Objekt ist hier zum Beispiel nur eine Sammlung aus einer Zeichenkette und einer Zahl (name bzw. pay) sowie Funktionen zur Verarbeitung dieser beiden eingebauten Objekte.

Die größere Geschichte der Klassen ist, dass ihr Vererbungsmechanismus Softwarehierarchien unterstützt, die sich für die Anpassung durch Erweiterung eignen. Wir erweitern Software, indem wir neue Klassen schreiben, nicht indem wir ändern, was bereits funktioniert. Du solltest auch wissen, dass Klassen eine optionale Funktion von Python sind und dass einfachere eingebaute Typen wie Listen und Wörterbücher oft bessere Werkzeuge sind als benutzerprogrammierte Klassen. Das alles würde jedoch den Rahmen unseres einführenden Objekttyp-Tutorials sprengen, also betrachte dies nur als Vorschau; für die vollständige Offenlegung von benutzerdefinierten Typen, die mit Klassen kodiert werden, musst du weiter lesen. Da Klassen auf anderen Werkzeugen in Python aufbauen, sind sie eines der Hauptziele dieses Buches.

Und alles andere

Wie bereits erwähnt, ist alles, was du in einem Python-Skript verarbeiten kannst, ein Objekttyp, so dass unsere Objekttyp-Tour zwangsläufig unvollständig ist. Aber auch wenn alles in Python ein "Objekt" ist, gehören nur die Objekttypen, die wir bisher kennengelernt haben, zum Kerntyp von Python. Andere Typen in Python sind entweder Objekte, die mit der Programmausführung zusammenhängen (wie Funktionen, Module, Klassen und kompilierter Code), die wir später untersuchen werden, oder sie werden durch importierte Modulfunktionen und nicht durch die Sprachsyntax implementiert. Letztere haben meist auch anwendungsspezifische Rollen - Textmuster, Datenbankschnittstellen, Netzwerkverbindungen und so weiter.

Außerdem solltest du bedenken, dass die Objekte, die wir hier kennengelernt haben, zwar Objekte sind, aber nicht unbedingt objektorientiert - einKonzept, das in der Regel Vererbung und die Python-Anweisung class voraussetzt, die wir später in diesem Buch noch einmal kennenlernen werden. Dennoch sind die Kernobjekte von Python die Arbeitspferde fast aller Python-Skripte, die du wahrscheinlich kennenlernen wirst, und sie sind in der Regel die Grundlage für größere Nicht-Kernobjekte.

Kapitel Zusammenfassung

Und das war's dann auch schon mit unserer ersten Datentyp-Tour. In diesem Kapitel haben wir eine kurze Einführung in die wichtigsten Objekttypen von Python und die Arten von Operationen gegeben, die wir auf sie anwenden können. Wir haben uns mit allgemeinen Operationen beschäftigt, die auf viele Objekttypen angewendet werden können (z. B. Sequenzoperationen wie Indizierung und Slicing), sowie mit typspezifischen Operationen, die als Methodenaufrufe verfügbar sind (z. B. String-Splits und List-Appends). Außerdem haben wir einige wichtige Begriffe wie Unveränderlichkeit, Sequenzen und Polymorphismus definiert.

Im Laufe der Zeit haben wir festgestellt, dass die Kernobjekttypen von Python flexibler und leistungsfähiger sind als die von niedrigeren Sprachen wie C. So machen die Listen und Wörterbücher von Python die meiste Arbeit überflüssig, die du in niedrigeren Sprachen für die Unterstützung von Sammlungen und die Suche benötigst. Listen sind geordnete Sammlungen anderer Objekte, und Wörterbücher sind Sammlungen anderer Objekte, die nach Schlüssel statt nach Position indiziert sind. Sowohl Wörterbücher als auch Listen können verschachtelt werden, sie können bei Bedarf wachsen und schrumpfen und können Objekte jeden Typs enthalten. Außerdem wird ihr Speicherplatz automatisch aufgeräumt, wenn du gehst. Wir haben auch gesehen, dass Strings und Dateien Hand in Hand arbeiten, um eine große Vielfalt an Binär- und Textdaten zu unterstützen.

Ich habe die meisten Details hier übersprungen, um dir einen schnellen Überblick zu verschaffen. Du solltest also nicht erwarten, dass du dieses Kapitel schon verstanden hast. In den nächsten Kapiteln werden wir tiefer einsteigen und die wichtigsten Objekttypen von Python ein zweites Mal durchgehen, um die hier ausgelassenen Details zu ergänzen und dir ein besseres Verständnis zu vermitteln. Das nächste Kapitel beginnt mit einem ausführlichen Blick auf die Zahlen in Python. Vorher gibt es aber noch ein Quiz zur Wiederholung.

Teste dein Wissen: Quiz

Wir werden die in diesem Kapitel vorgestellten Konzepte in den nächsten Kapiteln genauer untersuchen, daher werden wir hier nur die wichtigsten Ideen behandeln:

  1. Nenne vier der wichtigsten Datentypen von Python.

  2. Warum werden sie "Kern"-Datentypen genannt?

  3. Was bedeutet "unveränderlich" und welche drei Kerntypen von Python gelten als unveränderlich?

  4. Was bedeutet "Sequenz" und welche drei Typen fallen in diese Kategorie?

  5. Was bedeutet "Mapping", und welcher Kerntyp ist ein Mapping?

  6. Was ist "Polymorphismus" und warum sollte dich das interessieren?

Teste dein Wissen: Antworten

  1. Zahlen, Zeichenketten, Listen, Wörterbücher, Tupel, Dateien und Mengen werden im Allgemeinen als die wichtigsten Objekttypen (Daten) betrachtet. Auch Typen, None und Boolesche Werte werden manchmal auf diese Weise klassifiziert. Es gibt mehrere Zahlentypen (Ganzzahlen, Fließkommazahlen, komplexe Zahlen, Brüche und Dezimalzahlen) und mehrere String-Typen (einfache Strings und Unicode-Strings in Python 2.X und Text-Strings und Byte-Strings in Python 3.X).

  2. Sie werden als "Kerntypen" bezeichnet, weil sie Teil der Python-Sprache selbst sind und immer zur Verfügung stehen; um andere Objekte zu erstellen, musst du normalerweise Funktionen in importierten Modulen aufrufen. Die meisten Kerntypen haben eine spezielle Syntax, um die Objekte zu erzeugen: 'spam' ist zum Beispiel ein Ausdruck, der eine Zeichenkette erzeugt und die Menge der Operationen festlegt, die auf sie angewendet werden können. Deshalb sind die Kerntypen fest in der Syntax von Python verankert. Im Gegensatz dazu musst du die eingebaute Funktion open aufrufen, um ein Datei-Objekt zu erstellen (auch wenn dies normalerweise ebenfalls als Kerntyp gilt).

  3. Ein "unveränderliches" Objekt ist ein Objekt, das nach seiner Erstellung nicht mehr verändert werden kann. In Python fallen Zahlen, Strings und Tupel in diese Kategorie. Du kannst ein unveränderliches Objekt zwar nicht an Ort und Stelle ändern, aber du kannst jederzeit ein neues Objekt erzeugen, indem du einen Ausdruck ausführst. Bytearrays in neueren Python-Versionen bieten die Möglichkeit, Text zu ändern, aber es handelt sich dabei nicht um normale Strings, sondern nur dann, wenn es sich um einen einfachen 8-Bit-Text handelt (z. B. ASCII).

  4. Eine "Sequenz" ist eine geordnete Sammlung von Objekten. Strings, Listen und Tupel sind in Python alle Sequenzen. Sie haben gemeinsame Sequenzoperationen wie Indizierung, Verkettung und Slicing, verfügen aber auch über typspezifische Methodenaufrufe. Der verwandte Begriff "iterable" bezeichnet entweder eine physische Sequenz oder eine virtuelle Sequenz, die ihre Elemente auf Anfrage erzeugt.

  5. Der Begriff "Mapping" bezeichnet ein Objekt, das Schlüssel auf zugehörige Werte abbildet. Das Python-Wörterbuch ist der einzige Mapping-Typ im Kerntypensatz. Mappings sind nicht von links nach rechts geordnet, sondern ermöglichen den Zugriff auf Daten, die nach Schlüssel gespeichert sind, sowie typspezifische Methodenaufrufe.

  6. "Polymorphismus" bedeutet, dass die Bedeutung einer Operation (wie +) von den Objekten abhängt, auf denen sie ausgeführt wird. Das ist ein Schlüsselgedanke (vielleicht sogar der Schlüsselgedanke) bei der Verwendung von Python: Wenn du deinen Code auf bestimmte Typen beschränkst, ist er automatisch auf viele Typen anwendbar.

1 Entschuldige bitte meine Förmlichkeit. Ich bin Informatiker.

2 In diesem Buch steht der Begriff Literal einfach für einen Ausdruck, dessen Syntax ein Objekt erzeugt - manchmal auch als Konstante bezeichnet. Beachte, dass der Begriff "Konstante" nicht bedeutet, dass es sich um Objekte oder Variablen handelt, die niemals geändert werden können (d.h. dieser Begriff hat nichts mit C++'s const oder Python's "immutable" zu tun - ein Thema, das im Abschnitt "Unveränderlichkeit" behandelt wird ).

3 Diese Matrixstruktur eignet sich für kleinere Aufgaben, aber für ernsthaftere Zahlenberechnungen wirst du wahrscheinlich eine der numerischen Erweiterungen von Python verwenden wollen, wie z. B. die Open-Source-Systeme NumPy und SciPy. Diese Tools können große Matrizen viel effizienter speichern und verarbeiten als unsere verschachtelte Listenstruktur. Man sagt, dass NumPy Python zu einer kostenlosen und leistungsfähigeren Version des Matlab-Systems macht, und Organisationen wie die NASA, Los Alamos, JPL und viele andere nutzen dieses Tool für wissenschaftliche und finanzielle Aufgaben. Suche im Internet nach weiteren Details.

4 Hier zwei Anwendungshinweise. Erstens könnte der rec Datensatz, den wir gerade erstellt haben, ein echter Datenbankdatensatz sein, wenn wir Pythons Objektpersistenzsystem verwenden - eine einfache Möglichkeit, native Python-Objekte in einfachen Dateien oder Access-by-Key-Datenbanken zu speichern, die Objekte automatisch in serielle Byte-Streams umwandeln. Wir werden hier nicht ins Detail gehen, aber wir werden die Persistenzmodule von Python pickle und shelve in Kapitel 9, Kapitel 28, Kapitel 31 und Kapitel 37 im Zusammenhang mit Dateien, einem OOP-Anwendungsfall, Klassen bzw. 3.X-Änderungen behandeln.

Zweitens: Wenn du mit JSON(JavaScript Object Notation) vertraut bist - einem aufkommenden Datenaustauschformat, das für Datenbanken und Netzwerkübertragungen verwendet wird -, kann dieses Beispiel auch seltsam ähnlich aussehen, obwohl Pythons Unterstützung für Variablen, beliebige Ausdrücke und Änderungen seine Datenstrukturen allgemeiner machen kann. Pythons Bibliotheksmodul json unterstützt das Erstellen und Parsen von JSON-Text, aber die Übersetzung in Python-Objekte ist oft trivial. Achte auf ein JSON-Beispiel, das diesen Datensatz in Kapitel 9 verwendet, wenn wir uns mit Dateien beschäftigen. Ein größerer Anwendungsfall ist MongoDB, das Daten mithilfe einer sprachneutralen, binär kodierten Serialisierung von JSON-ähnlichen Dokumenten speichert, und seine PyMongo-Schnittstelle.

Get Python lernen, 5. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.