Zahlen: int und float¶
In Python ist kleingeschrieben int der Datentyp für ganze Zahlen (en. Integer).
x = 10
definiert eine Variable x vom Typ int, denn 10 hat den Typ int.
print(type(x))
# Output: <class 'int'>
Es gibt keinen long-Datentyp, denn int kann beliebig große Integer enthalten (sog. BigInteger), sodass es auch keinen Overflow geben kann.
Die Operatoren +,-,* und Modulo % verhalten sich wie erwartet, außerdem gibt es noch ganzzahlige Divison // und Division /:
9 // 4 == 2
9 / 4 == 2.25
9 % 4 == 1
10 // 5 == 2
10 / 5 == 2.0
10 % 5 == 0
An der Notation mit dem Trennpunkt erkennt man, dass der Typ nicht int ist, sondern float. Explizite Typumwandlung (type casting) macht man, indem man den Konstruktor für den Typ aufruft:
x = 2.9
y = int(x)
print(y, type(y))
# Output: 2 <class 'int'>
Da Variablen in Python dynamisch gebunden sind, kann man auch solchen Code ausführen:
x = 2.9
print(x, type(x))
# Output: 2.9 <class 'float'>
x = int(x)
print(x, type(x))
# Output: 2 <class 'int'>
Ansonsten verhält sich float wie ein Java-double. Zahlen mit Punkt werden automatisch als float gelesen, außerdem kann man wissenschaftliche Notation verwenden und Not-a-Number:
x = 1.2e10
print(x, type(x))
# Output: 1.2e10 <class 'float'>
x = float("nan")
print(x, type(x))
# Output: nan <class 'float'>
Python wandelt auch implizit Typen um, wenn nötig:
x = 5
y = 2.2
print(type(x), type(y), type(x + y), type(y + x))
# Output: <class 'int'>, <class 'float'>, <class 'float'>, <class 'float'>
String und bytes¶
Python unterscheidet zwischen Text (Sequenzen von abstrakten Zeichen) und Binärdaten (Sequenzen von Bytes). Für Text wird der Datentyp str (String) verwendet, der Zeichensatz ist Unicode.
Strings sind (im Gegensatz zu Listen von Zeichen) immutable, d.h. wenn man einen String verändert, ist das Ergebnis ein neuer String (wie auch bei int und float, das Objekt hat eine andere id = Speicheradresse), man kann also bei einer String-Variable nicht z.B. einzelne Zeichen im Speicher ändern. Anführungszeichen konstruieren Strings:
x = "test"
y = """Mehrzeiliger String
ist ebenfalls möglich"""
z = 'geht auch einfacher'
print(x, id(x), type(x))
# Output: test 140655159059952 <class 'str'>
x = x + 2
print(x, id(x), type(x))
# Output: test2 140655158905584 <class 'str'>
Zugriff auf einzelne Zeichen oder Substrings erhält man über sog. Slicing:
x = "Textaufschnitt"
print(x[0], x[2], x[-1], x[1:4], x[-4:-1])
# Output: T x t ext nit
Gelegentlich begegnet man Binärdaten, die eigentlich einen String kodieren sollen, z.B. in einem Encoding wie UTF-8 (der Standard für Python-Quelltexte). Das hängt so zusammen:
x = b"Binaerdaten"
y = x.decode('utf-8')
z = "Bin ein String".encode('utf-8')
print(type(x),type(y),type(z))
# Output: <class 'bytes'> <class 'str'> <class 'bytes'>
Beim Slicing auf ein einzelnes Zeichen bekommen wir int bei bytes und str bei str (es gibt keinen speziellen Character-Datentyp):
print(x[0], y[0], z[0])
# Output: 66 B 66
print(type(x[0]), type(y[0]))
# Output: <class 'int'> <class 'str'>
Während in normalen Strings Escape-Sequenzen wie \n in die entsprechenden Zeichen umgewandelt werden (also für \n ein Zeilenumbruch), lässt sich mit der Notation r"\n" auch die Zeichenfolge \n selbst als String deklarieren. Ansonsten könnte man auch den Backslash selbst escapen:
x = r"\n"
y = "\\n"
print(x, y, len(x), len(y))
# Output: \n \n 2 2
Wir haben damit auch ein Beispiel für zwei Objekte, die gleich sind (das selbe enthalten) aber nicht das selbe Objekt sind:
print(x == y, id(x) == id(y), x is y)
# Output: True False False
Der Python-Ausdruck x is y prüft, ob die Objekte identisch (dasselbe) sind, also die Pointer auf die gleiche Speicheradresse zeigen, also genau id(x) == id(y). Hingegen x == y ist syntaktischer Zucker für x.__eq__(y), also eine Methode des Datentyps String (in diesem Fall). Die Notation mit dem Doppel-Underscore __ wird dunder genannt; damit werden die Operatoren markiert, für die es solche syntaktischen Abkürzungen gibt.
Wenn man ein Python-Objekt darstellen will, etwa in Logfiles, kann man das auf zwei Weisen tun: Typecasting nach str sowie explizit eine menschenlesbare String-Repräsentation anfordern mit repr. Dabei ist die Idee hinter repr(x) dass dies ein String für Entwickler ist, idealerweise sodass die Interpretation dieses Strings durch den Python-Interpreter (mit eval) wiederum gleich zu x ist:
x = (42, 23)
y = repr(x)
z = eval(y)
print(type(x), type(y), z == x)
# Output: <class 'tuple'> <class 'str'> True
Listen, Tupel und Sets¶
Der Datentyp list ist eine geordnete, veränderbare, dynamische Sammlung (Collection) von Objekten. Das entspricht in Java eher einer ArrayList als einem Array.
liste = [1,2, "oh weia"]
print(liste, type(liste), id(liste))
# Output: [1, 2, 'oh weia'] <class 'list'> 140655157181696
liste[0] = "test"
print(liste, type(liste), id(liste))
# Output: ['test', 2, 'oh weia'] <class 'list'> 140655157181696
pointer_auf_liste = liste
liste[1] = "anders"
print(pointer_auf_liste)
# Output: ['test', 'anders', 'oh weia']
Die leere Liste wird mit [] oder list() erzeugt. Auch Listen haben mit len eine Längenabfrage und lassen sich über Slicing zugreifen und ändern:
liste = ["a", "b", "c", "d", "e"]
print(len(liste), liste[2], liste[2:3], liste[-4:-1])
# Output: 5 c ['c'] ['b', 'c', 'd']
liste[1:] = ["h","o","i"]
print(liste)
# Output: ['a', 'h', 'o', 'i']
liste.pop(0)
liste.pop(1)
liste.append("!")
print(liste)
# Output: ['h', 'i', '!']
Immutable Listen sind in Python Tupel, die mit runden Klammern gekennzeichnet werden:
tupel = (1, 2, "test")
print(tupel[1:], type(tupel))
# Output: (2, 'test') <class 'tuple'>
Wenn man ein Tupel versucht, zu verändern, gibt es einen TypeError ('tuple' object does not support item assignment).
Wenn es nicht auf die Reihenfolge ankommt und jeder Eintrag nur einmal vorkommen darf, arbeitet man mit einem set (geschweifte Klammern), das ist wie ein Java-HashSet. Wie auch in Listen und Tupel kann man in Sets mit in fragen, was enthalten ist:
menge = {2, 3, "vier"}
print(3 in menge, 4 in menge)
# Output: True False
Dictionaries¶
In Python werden sehr großzügig Key-Value-Maps verwendet, mit dem Datentyp dict (wir stellen uns ein Wörterbuch, en. dictionary, vor in dem zu jedem key eine value nachgeschlagen werden kann). Das Python dict verhält sich etwa wie eine Java-HashMap. Dictionaries sind eine veränderbare (mutable) Sammlung von Key-Value-Paaren.
Die Syntax für Dictionaries sind geschweifte Klammern und Doppelpunkte zwischen Key und Value:
x = {"key 1" : "value 1",
7 : "value for 7",
2.4 : "value for 2.4",
}
print(x[7], x[2.4], x["key 1"])
# Output: value for 7 value for 2.4 value 1Als Key sind immutable types erlaubt, etwa str, int, float wie im Beispiel, oder auch tuple. Werte können alle Python-Objekte sein und dürfen auch mehrfach auftreten. Das leere Dictionary erhält man mit {} oder dict(). Der dict-Konstruktor erlaubt auch, Keys direkt als Argumente anzugeben:
x = dict(key1 = 1, key2 = "zwei")
print(x)
# Output: {'key1': 1, 'key2': 'zwei'}
Wenn man versucht, auf eine Value zuzugreifen, für die der angefragte Key nicht im Dictionary ist, erhält man einen KeyError. Man kann mit default-Wert zugreifen und abfragen, welche Keys vorhanden sind:
print("key1" in x, "key3" in x)
# Output: True False
safe_value = x.get("key3", "default value")
print(safe_value)
# Output: default value
Seit Python 3.7 sind Dictionaries geordnet in der Reihenfolge des Einfügens, was relevant sein kann, wenn man über die Key-Value-Paare iterieren möchte. Im Kapitel über Kontrollstrukturen, Abschnitt über Schleifen, werden wir sehen, wie man über Sammlungen wie Listen, Tupel und Dictionaries iterieren kann.
Wahrheitswerte¶
In Python sind Wahrheitswerte mit dem Typ bool repräsentiert, Konstruktoren sind True und False. Eine Eigenart ist, dass bool als Subtyp von int implementiert ist, also
x = True
print(type(x), type(1), x == 1, x + 3)
# Output: <class 'bool'> <class 'int'> True 4
In der Python-Philosophie verwendet man selten explizites Typecasting mit bool(x) für ein Objekt x, aber jedes Objekt sollte zu einem Boolean evaluiert werden können; leere Sammlungen und numerische Nullwerte zu False, alles andere zu True. Daher ist auch die Frage len(x) > 0 ungewöhnlich, schreibt man doch eher x an der Stelle, wo implizit zu bool gecastet wird. Das sehen wir im Kapitel über Kontrollstrukturen, Abschnitt über if-then-else.
None¶
Es gibt in Python ein spezielles Objekt None welches Instanz der Singleton-Klasse NoneType ist. Das entspricht etwa Java-null. Es ist der default-Rückgabewert für Methoden. Wenn man Variablen deklarieren möchte, die noch nicht initialisiert sind, kann man dies mit None tun:
x = None
print(x, type(x), x is None)
# Output: None <class 'NoneType'> True
Anstelle von Maybe-Monaden oder Fragezeichen an der Typendeklaration lässt sich durch die dynamische Bindung in Python in jede Variable ein None einfüllen. Das kann nützlich, aber auch eine Fehlerquelle sein. Um zu prüfen, ob eine Variable Werte enthält, oder None ist, sollte man stets mit x is None arbeiten, nicht mit x == None oder Typecasting von x auf einen Wahrheitswert, obwohl bool(None) stets False ist.
Perspektivwechsel: Sprachspezifikation¶
Anstatt aus der Innenperspektive eines Typensystems zu denken, können wir ein Python-Programm auch als einen langen String (oder gar zunächst bytes) denken, und uns fragen, wie dieser umgewandelt wird. Woher weiss der Interpreter, wie eine Zeile Code umzuwandeln ist in Variablendeklarationen, Verzweigungen und Rechnungen? Um das gut nachvollziehen zu können, hilft es, einmal die Sprachspezifikation zu lesen, insb.
das Kapitel zur lexikalischen Analyse.