Datentypen und Variablen

Einführung

Haben Sie bereits in Sprachen wie C und C++ programmiert? Deswegen glauben Sie, dass Sie bereits genug über Datentypen und Variablen wissen? Sie wissen sicherlich viel, das stimmt, aber nicht genug, wenn es um Python geht. Deshalb lohnt es sich auf jeden Fall hier weiter zu lesen.

Es gibt haarige Unterschiede in der Art wie Python und C Variablen behandeln. Vertraute Datentypen wie Ganzzahlen (Integer), Fließkommazahlen (floating point numbers) und Strings sind zwar in Python vorhanden, aber auch hier gibt es wesentliche Unterschiede zu C und C++.

Möchte man Listen oder assoziative Arrays in C oder C++ verwenden, dann muss man sie sich selbst mühsam von Grund auf inklusive Zugriffs- und Suchfunktionen programmieren oder schwer verständlichen Libraries verwenden.

Dieses Kapitel ist also sowohl für Anfänger als auch für Fortgeschrittene Programmierer zu empfehlen.

Variablen

Unvermeidliche Entscheidungen Eine Variable im allgemeinsten Sinne ist einfach ein Behälter (Container) zur Aufbewahrung von bestimmten Werten, also z.B. Strings oder Zahlen. Wie der Name sagt, sind Variablen "variabel", d.h. sie können ihren Wert ändern. Man kann im Verlauf des Programms auf diese Variablen, oder genauer auf den Wert ihres Inhaltes zugreifen, oder ihnen einen neuen Wert zuweisen.
In den meisten Programmiersprachen, wie z.B. C, ist es so, dass eine Variable einen festen Speicherplatz bezeichnet, in dem Werte eines bestimmten Datentyps abgelegt werden können. Während des Programmlaufes kann sich der Wert der Variable ändern, aber die Wertänderungen müssen vom gleichen Typ sein. Also man kann nicht in einer Variable zu einem bestimmten Zeitpunkt eine Integerzahl gespeichert haben und dann diesen Wert durch eine Fließkommazahl überschreiben. Ebenso ist der Speicherort der Variablen während des gesamten Laufes konstant, kann also nicht mehr geändert werden. In Sprachen wie C wird der Speicherort bereit durch den Compiler fixiert.
In Python sieht dies anders aus. Zunächst einmal bezeichnen Variablen in Python keinen bestimmten Typ und deshalb benötigt man auch in Python keine Typdeklaration. Benötigt man im Programm bespielsweise eine Variable i mit dem Wert 42, so erreicht man dies einfach mit der folgenden Anweisung:

i = 42
Obige Anweisung darf man nicht als mathematisches Gleichheitszeichen sehen, sondern als "der Variablen i wird der Wert 42 zugewiesen", d.h. der Inhalt von i ist nach der Zuweisung 42. Man kann diesen Wert der Variablen auch, wie man im folgenden Beispiel sieht, anschließend ändern:
 >>> i = 42
>>> i = i + 1
>>> print(i)
43
>>> 

Verändern von Datentypen und Speicherplätzen

Programmieren bedeutet Verarbeitung von Daten. Daten werden in einem Python-Programm durch Objekte repräsentiert. Diese Objekte können in Python

Zahlen

Python kennt vier eingebaute (built-in) Datentypen für Zahlen:

Typwechsel bei Variablen

In Python kann eine Variable, wie bereits gesagt, sofort ohne Deklaration des Datentyps verwendet werden. Dennoch vergibt Python einen Datentyp, d.h. je nach Datentyp, wird die Variable anders angelegt, also als Integer, Float, String, und so weiter.
Der Datentyp ist in Python nicht an die Variable, sondern an den Wert gebunden, was impliziert, dass sich der Typ zur Laufzeit ändern kann, wie wir im folgenden Beispiel sehen können:
i = 42		# Datentyp ist integer (implizit)
i = 42 + 0.11	# Typ ändert sich zu float
i = "fourty"	# und jetzt ein String   

Wechselnde Speicherorte

Prinzipiell wird sich vorigen Fall, wobei das natürlich implementierungsabhängig ist, der Speicherort für die Variable i ändern. Der Interpretor kann bei der Anweisung "i = 42" den Wert als Integer abspeichern, muss aber bei der Anweisung "i = 42 + 0.11" einen neuen Ort für eine Float-Zahl anlegen. Für i = "fourty" muss er in einen String gewandelt werden.
Achtung: Als Anwender braucht man dies eigentlich nicht zu wissen, da ja alles automatisch geschieht!

Betrachten wir nun folgenden Python-Code:
>>> x = 3
>>> y = x
>>> y = 2
Variablen und
							 Speicherorte schematisch
Intuitiv würde man davon ausgehen, dass Python zunächst für x einen Speicherort wählt und dort das Objekt (Zahl) 3 abspeichert. Anschließend wird der Variablen y der Wert von x zugewiesen. In C und vielen anderen Programmiersprachen würde auch für y ein eigener Speicherort bestehen, in dem nun die Zahl 3 hineingeschrieben würde. Python geht anders vor: x ist eine Variable mit dem Objekt 3 und y ist eine Variable mit dem "selben" (nicht "gleichen") Objekt. x und y "zeigen" auf das gleiche Objekt. In der letzten Zeile wird y nun der Wert 2 zugewiesen, jetzt muss ein neues Objekt angelegt werden und y "zeigt" auf einen neuen Speicherort. (Anm: Dieses eben verwendete "zeigen" sollte von C-Programmierern keinesfalls mit den unter C verwendeten Pointern verwechselt werden.)

Es stellt sich nun die Frage, wie man das oben gesagte überprüfen kann. Dazu bietet sich die Identitätsfuntion id() an. Die Identität einer Instanz dient dazu, sie von allen anderen Instanzen zu unterscheiden. Die Identität ist eine Ganzzahl, und sie ist innerhalb eines Programmes eindeutig. Die Identitätsfunktion id() liefert die Identität. So kann man prüfen, ob es sich um eine bestimmte Instanz handelt und nicht nur um eine mit dem gleichen Wert und Typ. Wir geben nochmals das obige Beispiel ein, lassen uns aber jeweils die Identität ausgeben:
>>> x = 3
>>> print(id(x))
157379912
>>> y = x
>>> print(id(y))
157379912
>>> y = 2
>>> print(id(y))
157379924
>>> print(id(x))
157379912
>>> 
Wir stellen fest, dass sich die Identität erst ändert, nachdem wir y einen neuen Wert zugewiesen haben. Die Identität von x bleibt gleich, d.h. der Speicherort von x wird nicht geändert.

Besonderheiten bei Strings

Einen besonderen Effekt können wir bei Strings feststellen. Im folgenden Beispiel wollen wir dies veranschaulichen. Dazu benötigen wir noch den "is"-Operator. Sind a und b zwei Strings, dann prüft "a is b", ob a und b die gleiche Identität (Speicherort) haben. Wenn "a is b" gilt, dann gilt auch "a == b". Aber wenn "a == b" gilt, dann gilt natürlich nicht notwendigerweise auch "a is b"!
Nun wollen wir untersuchen, wie Strings in Python abgespeichert werden. Im folgenden Beispiel, erkennen wir, dass a und b sich den gleichen Speicherort teilen, obwohl wir diesmal keine Zuweisung der Art "b = a" verwendet haben:
>>> a = "Linux"
>>> b = "Linux"
>>> a is b
True
Wie sieht es jedoch aus, wenn der verwendete String länger ist? Im folgenden verwenden wir als String den längsten Ortsnamen der Welt. Eine Gemeinde mit etwas mehr als 3000 Einwohnern im Süden der Insel Anglesey in der gleichnamigen Grafschaft im Nordwesten von Wales:
>>> a = "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch"
>>> b = "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch"
>>> a is b
True
Aber Vorsicht ist geboten, denn was für eine Gemeinde in Wales funktioniert, schlägt beispielsweise für Baden-Württemberg fehl:
>>> a = "Baden-Württemberg"
>>> b = "Baden-Württemberg"
>>> a is b
False
>>> a == b
True
Also an der geographischen Lage kann es nicht liegen, wie man im folgenden Beispiel sieht. Es sieht vielmehr so aus, als dürften keine Sonderzeichen oder Blanks im String vorkommen.
>>> a = "Baden!"
>>> b = "Baden!"
>>> a is b
False
>>> a = "Baden1"
>>> b = "Baden1"
>>> a is b
True


String-Interna

Die Aufgabe der Computer der ersten Generation in den Vierziger- und Fünfzigerjahren des vorigen Jahrhunderts bestand - wegen technischer Restriktionen - im Wesentlichen in der Lösung numerischer Probleme. Textverarbeitung war zu dieser Zeit nur ein Traum. Heutzutage beschäftigen sich Computer in einem großen Umfang mit Textverarbeitung; die bedeutendste Anwendungen stellen wohl die Suchmaschinen, allen voran Google, dar. Um mit Texten umzugehen, benötigen Programmiersprachen geeignete Datentypen. So werden Strings in nahezu allen modernen Programmiersprachen genutzt um textuelle Informationen zu verarbeiten. Logisch gesehen ist eine String - wie Text - eine Folge von Zeichen. Die Frage bleibt, was ein Zeichen (englisch character) darstellt. In einem Buch oder auch einem Text, wie den, den Sie gerade lesen, werden die Zeichen grafisch dargestellt, sogenannte Grapheme, die aus Linien und Kurven bestehen, die in bestimmten Verhältnissen angeordnet sind, sich überschneiden und so weiter.

Der Buchstabe A in verschiedenen Fonts / Graphemen In der Computertechnik stellt ein Character eine Einformationseinheit dar. Die Zeichen (characters) entsprechen Graphemen, den zugrundeliegenden Einheiten der geschriebenen und gedruckten Sprache. Vor Unicode gab es nur eine Eins-zu-eins-Beziehung zwischen Zeichen und Bytes, d.h., jedes einzelne Zeichen wurde durch ein Byte im Computer repräsentiert. Ein solches Byte repräsentiert dann das logische Konzept des Zeichens und steht für die gesamte Klasse der Grapheme dieses Zeichens. In dem Bild auf der rechten Seite zeigen wir verschiedene grafische Repräsentationen des Buchstaben "A", also "A" in verschiedenen Fonts. Wir sehen, dass es im Druck verschiedene grafische Darstellungen des abstrakten Konzeptes "A" gibt. (Übrigens geht unser heutiges A auf eine ägyptische Hieroglyphe zurück, die einen Ochsenkopf symbolisiert)
Alle grafischen Darstellungen haben bestimmte Eigenschaften gemeinsam. Ein großes "A" wird in ASCII mit dem Byte-Wert 65 kodiert.
ASCII ist allerdings auf 256 Bytes oder Zeichen beschränkt. Das ist natürlich genug für Sprachen wie Englisch, Deutsch oder Französisch, aber bei weitem nicht genug für Schriftsysteme, wie sie das Chinesische oder das Japanische erfordern. Wegen dieser Probleme wurde Unicode ins Leben gerufen. Unicode ist ein Standard, der entworfen worden ist, um jedes beliebige Zeichen von jeder Schrift darstellen zu können. Verschiedene Schriftsysteme können damit auch gleichzeitig verwendet werden, so kann ein Text in lateinischer Schrift beispielsweise mit kyrillischem, arabischen oder sogar chinesischem Text gemischt werden. Jeder Buchstabe bzw. Zeichen wird in Unicode als eine 4-Byte große Zahl dargestellt, so entspricht beispielsweise das große "A" dem Wert U+0041. Während ASCII auf 256 pro Zeichensatz beschränkt ist, gibt es bei Unicode praktisch gesehen keinerlei Einschränkungen, denn mit 4 Bytes kann man 2564 (also mehr als 429 Millionen) abbilden. (Wegen der gewählten Zuordnung gibt es allerdings "nur" 1.112.064 mögliche Zeichen)
Nur wenig mehr als 100.000 Zeichen also weniger als 1% des theoretisch möglichen Zeichenraumes sind in Unicode belegt. Aber die Frage bleibt, wie man Unicode implementiert. Wir könnten natürlich 4 Bytes pro Zeichen verwenden und damit den Standard Ein-zu-eins umsetzen. Aber das wäre eine Speichervergeudung. Ein Textdokument würde dann viermal so viele Speicherplatz benötigen wie bisher unter ASCII.

Es gibt verschiedene Zeichncodierungen für Unicode:


Unicode-Kodierungen

Name Beschreibung
UTF-32
Hierbei handelt es sich um eine Eins-zu-eins Abbildung, d.h. jedes Unicode-Zeichen, was ja einer 4-Byte großen Zahl entspricht, wird auch in 4 Bytes abgespeichert. Ein Vorteil dieser Kodierung besteht darin, dass das n-te Zeichen in einem String in linearer Zeit berechnet (gefunden) werden kann, weil das n-te Zeichen immer an der Position 4×N vom Stringanfang aus gesehen beginnt. Der hohe Speicherverbrauch dieser Kodierung ist allerdings ein Problem.
UTF-16
Kaum jemand benötigt mehr als 65535, daher ist UTF-16, welches nur 2 Bytes benötigt, eine effizientere Alternative zu UTF-32.
Ein Problem, dass sowohl UTF-32 und UTF-16 betrifft besteht darin, dass die Byte-Reihenfolge eines Zeichens vom Betriebssystem abhängt.
UTF-8
UTF8 ist eine Kodierung für Unicode mit variabler Länge, d.h. verschiede Zeichen können verschiedene Kodierungslängen haben. So benötigen ASCII-Zeichen nur ein Byte pro Zeichen, die sogar dem original ASCII-Zeichensatz für die ersten 128 Zeichen entsprechen. Das bedeutet, dass diese Zeichen zwischen UTF-8 und ASCII ununterscheidbar sind. Aber die sogenannten "Erweiterten Lateinischen" Zeichen, denen beispielsweise die Umlaute wie ä, ö, ü angehören, benötigen zwei Bytes. Chinesische Zeichen benötigen drei und nur ganz spezielle extrem selten benutzte Spezialzeichen benötigen 4 Bytes in UTF-8.
Ein Nachteil dieser Methode besteht darin, das n-te Zeichen eines Strings zu bestimmen. Die Berechnungszeit ist dann nicht mehr linear.

Benutzung von Strings

Ein String, oder Zeichenkette, kann man als eine Sequenz von einzelnen Zeichen sehen.

String Indizierung

Jedes einzelne Zeichen eines Strings, kann über einen Index angesprochen werden. Im folgenden Beispiel sehen wir, wie der obige im Bild dargestellt String in Python definiert wird und wie wir auf ihn zugreifen können:

>>> s = "Python"
>>> print(s[0])
P
>>> print(s[3])
h
Die Länge eines Strings kann man mit der Funktion len() bestimmen und damit kann man auch einfach beispielsweise auf das letzte oder vorletzte Zeichen eines Strings zugreifen:
>>> s = "Python"
>>> index_last_char = len(s) - 1
>>> print(s[index_last_char])
n
>>> print(s[index_last_char - 1])
o
>>> 
Da es bei der praktischen Arbeit sehr häufig vorkommt, dass man auf einzelne Zeichen eines Strings von hinten zugreifen muss, wäre es sehr lästig, wenn man dies immer über den Umweg durch den Aufruf der Funktion len() machen müsste. Python bietet deshalb eine elegantere Möglichkeiten. Die Indices werden auch von rechts durch negative Indices nummeriert, d.h. das letzte Zeichen wird mittels des Index -1, das vorletzte mittels -2 usw. angesprochen. Wir sehen dies in der folgenden Abbildung veranschaulicht:

String Index negativ

Im Code sieht das wie folgt aus:
>>> s = "Python"
>>> last_character = s[-1]
>>> print(last_character)
n


Strings können unter Benutzung von angegeben werden.

Wichtige Stringfunktionen

Ein paar String-Funktionen:

Unveränderliche Zeichenketten

Wie in Java aber nicht wie in C oder C++, können String in Python nicht verändert werden. Versucht man eine indizierte Position zu ändern, erzeugt man eine Fehlermeldung:
>>> s = "Some things are immutable!"
>>> s[-1] = "."
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> 

Escape- oder Fluchtzeichen

Es gibt Zeichenfolgen, die den Textfluss steuern, wie zum Beispiel ein Newline (Zeilenvorschub) oder Tabulator. Sie lassen sich nicht auf dem Bildschirm als einzelne Zeichen darstellen. Die Darstellung solcher Zeichen innerhalb von String-Literalen erfolgt mittels spezieller Zeichenfolgen, sogenannter Escape-Sequenzen. Eine Escape-Sequenz wird von einem Backslash \ eingeleitet, gefolgt von der Kennung des gewünschten Sonderzeichens. Übersicht der Escape-Zeichen: Die Auswertung von Escape-Zeichen kann man verhindern, indem man einem String ein r oder R unmittelbar voranstellt.
Beispiel:
r"\n bewirkt einen Zeilenvorschub"

Byte-Strings

Python 3 benutzt die Konzepte Text und (binäre) Daten, statt Strings und 8-bit Strings wie in Python 2.x. Jeder String oder Text in Python 3 ist Unicode, aber kodiertes Unicode wird als Datenstrings in Bytes dargestellt. Der Datentyp für Text ist str; der Datentyp für Daten ist bytes. Es ist nicht möglich Text und Daten (bytes) zu mischen. Versucht man zu mischen, erhält man den Ausnahmefehler TypeError.
Während ein String-Objekt eine Folge von Zeichen enthält, enthält ein byte-Objekt eine Folge von Bytes mit den Werten 0 .. 255, die den ASCII-Werten entsprechen. Während ein String-Objekt eine Folge von Zeichen in Unicode enthält, enthält ein Byte-Objekt eine Folge von Bytes, aus dem Bereich 0 bis 255, die die ASCII-Werte repräsentieren.
Zur Verdeutlichung ein Beispiel:
>>> x = b"Hallo"
>>> t = str(x)
>>> u = t.encode("UTF-8")
In Python 3.x kann man nicht mehr explizit einen Unicode-String durch Voranstellung eines "u" definieren. Dies ist natürlich auch nicht notwendig, da String ohnehin automatisch Unicode sind:
>>> txt = u"Geht nicht"
  File "<stdin>", line 1
    txt = u"Geht nicht"
                      ^
SyntaxError: invalid syntax
>>>