Python 3 Crashkurs
27.02.2018, 12:41 - Autor: PGD
Warum Python?
Im Grund ist Python eine sehr einfach zu lernende Sprache. Die folgenden
Punkte beschreiben den Ansatz von Python sehr gut:
- Die Aufteilung des Codes in Blöcke (was das ist lernen wir in Kürze)
erfolgt durch Einrückungen. Dies zwingt den Entwickler übersichtlich formatierten
und gut lesbaren Code zu schreiben.
- In der Regel wird versucht einen Lösungsweg für ein Problem anzubieten.
Auch das sorgt dafür, dass Code einfacher und verständlicher wird - vor
allem Code den man nicht selbst geschrieben hat. Mangels anderer
Lösungswege werden unterschiedliche Entwickler Probleme auf die gleiche
oder zumindest recht ähnliche Weise lösen und das hilft dabei sich in
fremden Code schneller einzuarbeiten.
- Die Syntax ist einfach leesbar und sehr allgemeinverständlich gehalten.
Darum kann jeder mit Englisch-Grundkenntnissen mit ein klein wenig Fantasie
Python-Code zumindest ansatzweise verstehen und interpretieren. So stellen
sich sehr schnell Lernerfolge ein.
- Python erlaubt den Zugriff auf mehr als 100.000 Module, die Funktionen und
Klassen (auch das besprechen wir in Kürze) für fast jede Aufgabe zur
Verfügung stellen.
- Falls nötig kann man in einem gewissen Maße maschinennahe programmieren.
All das macht Python so beliebt - mit wenigen Zeilen Code können Sie in
Windesweile kleine Tools schreiben.
Für die folgenden Beispiele verwende ich die Python-IDLE. Wenn wir diese
Starten erhalten wir eine interaktive Python-Shell mit der wir im folgenden
Arbeiten werden.
user@kali:~$ python3
Python 3.6.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Datentypen & Variablen
a) None
Der Datentyp None kennzeichnet eine Variable als nicht gesetzt und wird wie
folgt angelegt:
>>> n = None
Eine Variable ist quasi ein Wert im Speicher, der über einen Namen
angesprochen werden kann. Eine Variable wird mit dem Schema
[NAME] = [WERT]
angelegt.
>>> 2var = 2
SyntaxError: invalid syntax
Der Name darf die Zeichen A-Z, a-z, _ und 0-9 enthalten und muss mit einem
_ oder Buchstaben beginnen.
>>> var2 = 2
>>> print(var2)
2
>>> print(Var2)
Traceback (most recent call last):
File "", line 1, in
Var2
NameError: name 'Var2' is not defined
Variablennamen sind Case-sensitive, sprich es wird zwischen Groß- und
Kleinschreibung unterschieden. Daher meldet der Python-Interpreter an
dieser Stelle auch, dass
Var2 nicht definiert ist.
>>> var2 = None
Außerdem kann man so eine Variable bewusst wieder auf den Status undefiniert
setzen um einen Wert zu löschen.
>>> var2 = None
>>> 2 * var2
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'
>>> var2 = 0
>>> 2 * var2
0
Das macht vor allem dann Sinn wenn mit der Variable gerechnet werden soll
und wir verhindern wollen, dass versehentlich mit falschen
Rechenergebnissen weiter gearbeitet wird.
Ist der Wert von
var2 auf
None gesetzt dann bricht das Programm die
Berechnung
2 * var2 mit einer Fehlermeldung ab. Der
Type-Error erklärt uns
kurz und knapp, dass
int-Werte nicht mit
None-Werten multipliziert werden können.
Hätten wir zum resetten der Variable einfach nur 0 verwendet würde die
Berechnung durchgeführt und das Ergebnis wäre logischer Weise dann auch
wieder 0.
b) int
Integer-Werte (kurz int) sind Ganzzahlen.
>>> a = 1
>>> b = 0xF
>>> a + b
16
>>> c = 0b10
>>> a + c
3
Hierbei ist die dezimale, hexadezimale (zB
0xF) und binäre (zB
0b10) Schreibweise erlaubt.
Das hexadezimale Zahlensystem basiert nicht auf 10 sondern auf 16. Hierbei
werden die Buchstaben A-F verwendet um die zahlen 10-15 darzustellen. Somit
entspricht
0xF der Dezimalzahl 15. Binärzahlen basieren auf 2 und daher bedeute
0b10
ein 2er und null 1er also die Dezamalzahl 2.
c) float
Float sind Fließkommazahlen. Diese Werte werden in der englischen
Schreibweise (Punkt statt Komma) angelegt.
>>> a = 1.5
>>> b = 2
>>> a + b
3.5
>>> a + a
3.0
>>> a * 2
3.0
>>> b / 2
1.0
Natürlich kann man
int- und
float-Werte in einer Berechnung mischen. Wenn ein
float-Wert in der Berechnung vorkommt ist das Ergebnis wieder ein
float-Wert unabhängig davon, ob das Ergebnis eine Ganzzahl wäre oder nicht!
Eine Division liefert immer einen
float-Wert unabhängig davon, ob das Ergebnis eine Ganzzahl wäre oder nicht!
d) String
Zeichenketten (auch Strings genannt) sind
>>> a = hallo
Traceback (most recent call last):
File "", line 1, in
NameError: name 'hallo' is not defined
Ohne Anführungzeichen wird das Wort hallo als Variablenname interpretiert
und versucht der Variablen
a den Wert von der Variablen
hallo zuzuweisen.
Da diese nicht existiert tritt der
NameError auf.
>>> a = "hallo"
>>> b = 'welt'
>>> a + " " + b
'hallo welt'
Die Zeichenkette kann in einfachen oder doppelten Anführungzeichen
eingeschlossen werden. Darüber hinaus kann man Strings mit dem
+ Operator
zusammenfügen.
>>> csv = "Max Muster,Musterstr. 1,1010,Wien,01/123456"
>>> csv.split(",")
['Max Muster', 'Musterstr. 1', '1010', 'Wien', '01/123456']
Alles in Python ist ein Objekt - daher kann auch auf einem String eine
sogenannte Methode angewandt werden. Hier in diesem Beispiel trennen wir
eine CSV-Zeile mit
split() an den Beistrichen auf und erzeugen daraus eine
Liste. Natürlich gibt es viele weitere Methoden die auf einen String angewendet werden können.
e) Listen
Listen sind eine Ansammlung von Werten, die zusammen unter dem gleichen
Namen angesprochen werden können. Dies kann Sinn machen wenn die Werte zB
zu einem Datensatz gehören. Auf die Einzel-Werte wird dann mit einer
Index-Zahl zugegriffen.
>>> l = [1, 2.3, "abc", None]
In Listen können auch verschiedene Datentypen gemischt werden.
>>> l.append(4)
>>> print(l)
[1, 2.3, 'abc', None, 4]
Mit
append() lassen sich Werte an die Liste anhängen.
>>> l.append([5,6,7])
>>> print(l)
[1, 2.3, 'abc', None, 4, [5, 6, 7]]
Listen können außerdem verschachtelt werden.
>>> print(l[0])
1
>>> print(l[2])
abc
>>> print(l[5][0])
5
>>> print(l[5][2])
7
Der Zugriff erfolgt wie gesagt mit einer Index-Zahl. Listen-Elemente werden
beginnend bei der
0 fortlaufend nummeriert. Somit greifen wir mit
l[0] auf
das erste und mit
l[2] auf das dritte Element der Liste zu.
Bei diesem Beispiel ist
l[5] (das sechste Element) wiederum eine Liste -
daher spricht
l[5] die ganze Liste an und
l[5][0] beispielsweise das erste
Element dieser Liste. Elemente können dann mit
l[5].append(...) angehängt
werden.
>>> print(len(l))
6
Mit
len() lässt sich die Länge bzw. Anzahl der Elemente einer Liste oder
vieler anderer Datentypen ermitteln. Bei einem String wird zB die
Zeichenanzahl geliefert.
>>> print(l[1:3])
[2.3, 'abc']
Mit der Schreibweise
1:3 als Index wird ein sogenanntes Slicing
durchgeführt - damit wird ein Teil einer Liste oder Zeichenkette
extrahiert. Hier bedeutet
1:3 eine Teilliste vom zweiten Element
(Index 1) bis exklusive dem vierten Element (Index 3). Daher werden die
Elemente mit dem Index 1 und 2 zurückgeliefert.
>>> l.pop()
[5, 6, 7]
... liefert das letzte Element der Liste zurück und entfernt es auch gleich
aus der Liste. So können Listen auch als LIFO-Stapel (Last In First Out)
verwendet werden.
>>> l.pop(0)
1
Analog dazu verhält sich
pop(0) bei dem ersten Listenelement und erlaubt
somit die Verwendung der Liste als FIFO-Stapel (First in First Out).
f) Tupel
Tupel sind deutlich performanter als Listen. Dafür sind Sie aber deutlich
weniger flexibel. Das nachträgliche Einfügen oder Löschen von Elementen ist
nicht erlaubt.
>>> t = (1, 2.3, (4, 5, 6), "bla")
>>> print(t)
(1, 2.3, (4, 5, 6), 'bla')
Ein Tupel wird mit
( und
) gebildet. Auch hier ist das mischen
verschiedenster Datentypen und das Verschachteln erlaubt.
>>> print(t[0])
1
>>> print(t[1:3])
(2.3, (4, 5, 6))
Der Zugriff erfolgt wie bei Listen über die Indexzahl und auch Slicing ist
möglich.
>>> print(t[-1])
bla
>>> t = t[0:-1]
>>> print(t)
(1, 2.3, (4, 5, 6))
Im Grunde ist Slicing auch eine Möglichkeit mit ein Tupel zu manipulieren.
Mit
t[-1] wird das letzte Element angesprochen und
t[0:-1] liefert alle bis
auf das letzte Element. Überschreibt man den Tupel mit dem Slice so wie
hier gezeigt hat man das letzte Element entfernt.
Mit zwei Slicing-Operationen haben wir quasi die
pop()-Methode nachgebaut.
g) Set
Ein Set ist eine besondere Form einer Liste. Sie ist performant wie ein
Tupel und hat die Eigenart, dass jeder Wert in der Liste nur einmalig
vorkommen darf.
>>> s = {"a", "c", "d"}
Gebildet wird ein Set mit den geschweiften Klammern. Ein leeres Set wird
mit
s = set() angelegt da die geschweiften Klammern auch für Dictionaries
verwendet werden.
s = {} würde hingegen ein leeres Dictionary erzeugen.
>>> s.add("a")
>>> s.add("d")
>>> s.add("b")
>>> print(s)
{'a', 'c', 'd', 'b'}
Da
a und
d schon im Set
s vorhanden sind werden diese Zahlen nicht noch
einmal hinzugefügt. Die Aufrufe von
s.add("a") bzw.
s.add("d") werden
kommentarlos ignoriert.
>>> s.add("g")
>>> s.add("f")
>>> print(s)
{'a', 'c', 'd', 'b', 'f', 'g'}
Das Hinzufügen von
g und
f klappt wiederum. Wobei die Elemente nicht
unbedingt in der Reihenfolge in der Sie hinzugefügt wurden im Set landen.
Diese Eigenarten des Sets gilt es bei der Verwendung immer zu bedenken um
Fehler zu vermeiden. Im Grunde ist dieser Datensatz lediglich für einige
spezielle Anwendungen sinnvoll - dort aber erspart er dem Entwickler
einiges an Arbeit!
h) Dictionaries
Wir haben bis dato immer mit einer Index-Ziffer auf die Werte zugegriffen.
Bei einfachen Strukturen mag das noch weniger Problematisch sein aber wie
sieht es hier aus:
>>> saison = ["01.01.2018-31.03.2018", "01.04.2018-31.12.2018"]
>>> zimmer = ["Standard", "Suite"]
>>> hotels = ["Ibis", "Hilton"]
>>> preise = [[[79, 89], [179, 189]], [[249, 299], [449, 499]]]
Sehen wir uns die folgende Preisliste an dann fällt uns auf, dass wir die
Preisliste nicht eindeutig lesen können ohne die Struktur der Daten zu
kennen und selbst wenn wir wüssten, dass der Aufbau
PREIS[Hotel][Zimmer][Saison] ist, ist die Preisliste nicht gerade einfach
zu lesen. Oder können Sie adhoc sagen was eine Suite im Ibis in der Saison
01.04.-31.12.2018 kostet?
Darüber hinaus benötigen wir 4 Listen um alle Daten abbilden zu können.
Genau darum gibt es Dictionaries - Sehen wir uns die gleiche Preiliste als
Dictionary an:
>>> d = {
... "Ibis": {
... "Standard": { "01.01.2018-31.03.2018": 79, "01.04.2018-31.12.2018": 89 },
... "Suite": { "01.01.2018-31.03.2018": 179, "01.04.2018-31.12.2018": 189 }
... },
... "Hilton": {
... "Standard": { "01.01.2018-31.03.2018": 249, "01.04.2018-31.12.2018": 299 },
... "Suite": { "01.01.2018-31.03.2018": 449, "01.04.2018-31.12.2018": 499 }
... }
... }
Deutlich übersichtlicher! Aber auch die Abfragen können wir deutlich
lesbarer formulieren:
>>> print(d['Ibis']['Suite']['01.04.2018-31.12.2018'])
189
Damit können wir also festhalten, dass dieser Datentyp dazu dient zwei
Werte miteinander in Beziehung zu setzen und dadurch ganz nebenbei die
Lesbarkeit bei komplexeren Strukturen enorm steigert.
Der generelle Aufbau ist:
{Schlüssel1 : Wert1, Schlüssel2 : Wert2}
Hierbei können Schlüssel entweder Zahlen oder Zeichenketten sein. Ein
Schlüssel muss in seiner Ebene allerdings eindeutig sein:
>>> d2 = {"a" : "text1", "b" : "text2", "a" : "text3"}
>>> print(d2)
{'a': 'text3', 'b': 'text2'}
Auch hier werden wir nicht gewarnt wenn wir einen Schlüssel doppelt
vergeben. Der Wert wird einfach kommentarlos überschrieben!
i) Boolean
Wahrheitswerte können nur die zwei Werte
True (wahr) oder
False (falsch)
annehmen.
>>> password_found = False
Meist werden Sie verwendet um einen Zustand zu beschreiben oder
>>> 3 > 5
False
>>> 3 < 5
True
um den Wahrheitswert von Vergleichen zu ermitteln.
Operatoren
a) Mathematische Operatoren
>>> # Addition
>>> print(1 + 2)
3
>>> # Subtraktion
>>> print(4 - 3)
1
>>> # Mulpiplikation
>>> print(5 * 6)
30
>>> # Division
>>> print(7 / 8)
0.875
Soweit sollten die Ausgaben der Grundrechnungsarten nicht besonders
verblüffen. Natürlich kann man nicht nur mit selbst eingegebenen Zahlen
sondern auch mit den Werten von Variablen rechnen.
>>> # 10 geteilt durch 3 ergibt 3 Rest 1
>>> # Ganzzahldivision
>>> print(10 // 3)
3
>>> # Modulo
>>> print(10 % 3)
1
Das
%-Zeichen ist der Modulo-Operator. Dieser liefert den Rest der
Division. Die Ganzzahldivision arbeitet wie der Modulo-Operator nur wird
hier das Ergebnis und nicht der Rest geliefert.
>>> # 3 mal 1 ist 3 plus 1 ergibt 4
>>> print(1 + 1 * 3)
4
>>> # 1 plus 1 ist 2 mal 3 ergibt 6
>>> print((1 + 1) * 3)
6
Auch in Python gilt Punkt- vor Strichrechnung. Wenn wir von dieser Regel
abweichen müssen oder wollen, dann ist das mit einer entsprechenden
Klammerung möglich. Die Berechnungen in der Klammer werden immer zuerst
ausgeführt.
>>> h = "Hallo"
>>> w = "Welt"
>>> print(h + " " + w)
Hallo Welt
Der
+ Operator kommt auch im Verbindung mit Texten zum Einsatz. In diesem
Fall werden Texte aneinandergereiht. Wir müssen uns unbedingt merken, dass
Operatoren abhängig von den Datentypen anders arbeiten.
>>> # 10 hoch 3 ist 1000
>>> print(10 ** 3)
1000
Die Exponentiation multipliziert die erste Zahl mit sich selbst. Die Anzahl
dieser Multiplikationen wird mit der zweiten Zahl festgelegt.
b) Bitweise Operatoren
>>> a = 10
>>> b = 6
>>> # Bitweise Und-Verknüpfung AND
>>> print(a & b)
2
>>> # Bitweise Oder-Verknüpfung OR
>>> print(a | b)
14
>>> # Bitweise Exklusiv-Oder-Verknüpfung XOR
>>> print(a ^ b)
12
Schauen wir uns einmal die Zahlen 2 und 6 binär an:
AND OR XOR
10 1010 1010 1010
6 0110 0110 0110
Ergebnis 0010 1110 1100
Die Binärzahl 0110 kann man als 0 mal 8 + 1 mal 4 + 1 mal 2 + 0 mal 1 lesen.
Die
AND-Verknüpfung ist nur dann erfüllt wenn an beiden Stellen eine Eins
steht. Daher ist das Ergebnis 0010 oder 2 in dezimaler Schreibweise.
Bei der
OR-Verknüpfung entsteht im Ergebnis eine Eins wenn an einer oder
beiden Stellen eine Eins steht. Derher ergibt sich wieder 6 bzw. 0110.
Beim
XOR ist die Bedingung nur erfüllt, wenn an einer der Stellen eine Eins
und an der anderen eine Null steht. Stünden an beiden Stellen Einsen oder
Nullen dann ergibt das wieder 0. Dadurch kommt das Ergebnis 4 (0100) zu
Stande.
>>> print(b >> 1)
3
>>> print(b << 1)
12
Der Schiebe-Operator verschiebt die Bits um eine Anzahl von Stellen (hier
1) nach Links oder Rechts.
Ursprungswert >> 1 << 1
Binär 0110 0011 1100
Dezimal 6 3 12
c) Logische Operatoren
Logische Operatoren werden in der Regel dazu verwendet, Vergleiche zu
Verknüpfen und arbeiten mit Boolean-Werten.
Wert 1 True True False False
Wert 2 True False True False
and True Flase False False
Der
and-Operator ist nur dann erfüllt, wenn beide Werte True sind. In jedem
anderen Fall wird False geliefert.
Wert 1 True True False False
Wert 2 True False True False
or True True True False
Der
or-Operator liefert True wenn einer oder beide Werte True sind und nur
dann ein False wenn beide Werte False sind.
Wert 1 True True False False
Wert 2 True False True False
!= False True True False
Wenn Sie nun einen logischen
xor-Operator vermissen, dann kommt hier wieder
die Vereinheitlichung der Lösungswege von Python durch. Diesen logischen
Operator gibt es nicht aber dafür kann der Ungleich-Operator
!= verwendet
werden.
>>> print(5 != 6)
True
>>> print(bool(5) != bool(6))
False
Natürlich ist 5 nicht gleich 6 und daher ist die erste Ausgabe vollkommen
logisch. Bei der Umwandlung in den Typ Boolean werden alle posiiven und
negativen Werte zu
True. Lediglich der Wert
0 wird zu
False.
>>> a = 3 > 2
>>> a
True
>>> not a
False
>>> b = not 3 > 2
>>> b
False
Der Operator
not negiert einen Boolean-Wert und liefert das Gegenteil.
d) Vergleichsoperatoren
Diese Operatoren dienen dazu Werte miteinander zu vergleichen. Dazu zählen
die folgenden Operatoren:
< ... kleiner
<= ... kleiner oder gleich
> ... größer
>= ... größer oder gleich
!= ... ungleich
== ... gleich
is ... gleiche Instanz
in ... Element einer Liste / Teilstring
Die Operatoren lassen sich sowohl auf Strings
>>> print("aab" > "aad")
False
>>> print("aab" < "aad")
True
>>> print("aab" >= "aad")
False
>>> print("aab" <= "aad")
True
>>> print("aab" == "aad")
False
>>> print("aab" != "aad")
True
als auch auf Zahlen anwenden
>>> print(5 > 4)
True
>>> print(5 < 4)
False
>>> print(5 >= 4)
True
>>> print(5 <= 4)
False
>>> print(5 == 4)
False
>>> print(5 != 4)
True
Bei Strings wird dem Zahlenwert der jeweils ersten Buchstaben verglichen,
stimmt dieser überein, wird der selbe Vergleich mit den jeweils zweiten
Buchstaben gemacht. Dies wird bis zu einem Unterschied durchgeführt oder
bis die ganzen Zeichen durchlaufen sind.
>>> a = 5
>>> b = 2.5 * 2
>>> print(a == b)
True
>>> print(a is b)
False
Der Vergleichsoperator (
==) arbeitet lediglich mit den Werten. Daher
überrascht es nicht, dass
5 das gleiche ist wie
2 * 2.5.
Der
is-Operator hingegen prüft ob die ID des Wertes im Speicher
übereinstimmt. Dazu müssen wir uns kurz ansehen wie Python Werte im
Speicher ablegt.
>>> a = 5
>>> b = 3 + 2
>>> print(id(a))
4297644544
>>> print(id(b))
4297644544
>>> print(a is b)
True
Beide Variablen zeigen also auf den gleichen Wert im Speicher. Python
versucht so effizient wie möglich zu arbeiten und da es nach der Ausführung
von
a = 5 den Wert
5 schon im Speicher gibt macht es wenig Sinn für eine
weitere
5 im Speicher Platz zu reservieren und den Wert doppelt
vorzuhalten. Es ist schneller in dem Fall beide Variablen auf die gleiche
ID im Speicher zeigen zu lassen.
>>> b = 2.5 * 2
>>> print(id(b))
4322300120
>>> print(a is b)
False
Ändert sich der Wert oder Datentyp von
b nun dann wird die
5.0 im Speicher
mit einer eigenen
ID angelegt und
b mit der neuen
ID verknüpft.
Daher kann der
is-Operator auch sicherstellen, dass eine Variable nicht nur
den gleichen Wert hat, sondern auch vom gleichen Datentyp ist. Das muss ja
auch so sein, wenn beide Variablen den gleichen Bereich im Speicher
referenzieren.
Alternativ wäre auch
print(type(a) == type(b))
eine Prüfung des Datentyps alleine machbar.
e) Zuweisungsoperator
Ohne zu wissen wie dieser Operator heißt haben wir damit schon sehr oft
gearbeitet... Das einfache
= Zeichen wird verwendet um einer Variable einen
Wert zuzuweisen.
>>> var1 = "Wert"
>>> var2 = 123
>>> print(var2)
123
>>> var2 = var1
>>> print(var2)
Wert
>>> print(id(var1))
4324866008
>>> print(id(var2))
4324866008
Hierbei können wir einen Wert direkt zuweisen (
var2 = 123) oder den Wert
einer Variable zuweisen (
var2 = var1).
>>> var2 = "abc"
>>> print(var1 + " " + var2)
Wert abc
>>> print(id(var1))
4324866008
>>> print(id(var2))
4320943832
Wenn wir einer Variable den Wert einer anderen zuweisen (
var2 = var1) dann
sind diese Variablen danach immer noch unabhängig von einander... Wenn in
weiterer Folge
var2 wieder geändert wird (
var2 = "abc") bleibt
var1 davon
unberührt.
Anfänger werden oft davon verwirrt, dass nach einer Zuweisung wie
var2 =
var1 beide Variablen auf die gleiche
ID zeigen. Dies ist jedoch nur
temporär solange bis sich der Wert von
var2 ändert. Sobald das geschieht
wird ein neuer Speicherbereich mit einer neuen
ID erstellt und
var2
zugewiesen.
var1 bleibt wie man sieht davon unberührt.
Typenkonvertierung
In vielen Fällen, wenn es eindeutig klar ist was der Entwickler will, nimmt
uns Python die Typenkonvertierung ab. Dies ist im Vergleich zu manch
anderer Programmiersprache recht komfortabel.
>>> a = 10
>>> b = 10
>>> print(str(b) + " == " + str(type(b)))
10 == <class 'int'>
>>> b = a * 2.1
>>> print(str(b) + " == " + str(type(b)))
21.0 == <class 'float'>
>>> b = a * 3.55
>>> print(str(b) + " == " + str(type(b)))
35.5 == <class 'float'>
>>> b = "3.55"
>>> print(b + " == " + str(type(b)))
3.55 == <class 'str'>
Beim überschreiben der Variable von Hand wie in den letzten drei
Zuweisungen wird der Typ automatisch konvertiert. Auch hier haben wir einen
Vorteil gegenüber manch anderen Sprachen wo man den Variablentyp schon beim
ersten Anlegen vordefinieren muss und alle Konvertierungen von Hand
vornehmen muss.
Bei anderen Fällen ist aber nicht eindeutig klar, was Python machen soll...
>>> s = "b"
>>> print(s + 3)
Traceback (most recent call last):
File "", line 1, in
TypeError: Can't convert 'int' object to str implicitly
Der
TypeError sagt uns, dass wir einen String brauchen. In diesem Fall ist
die Meldung wenig hilfreich weil diese den Fehler nicht wirklich
beschreibt. Besser wäre eine Meldung die sagt, dass eine eindeutige
Typenkonvertierung von Hand nötig ist.
Wir versuchen an dieser Stelle
b und
3 mit dem + Operator zu verbinden.
Wenn wir uns zurückerinnern dann kann der
+ Operator Zahlen addieren und
Strings verbinden. An dieser Stelle liefern wir dem Interpreter aber eine
Zahl und einen String also weiß er nicht ob er nun
b3 liefern soll,
b als
Hexadezimalwert für
11 nehmen soll und damit
14 liefern oder sollte er gar
den
ASCII-Wert von
b - die Zahl
98 - mit
3 addieren und dann
101 als
Ergebnis bzw. e liefern.
Daher sagt uns Python mit einem
TypeError "Lieber Programmierer deine
Anweisung ist mehrdeutig - kümmere dich selber um die Typenkonvertierung".
Wenn wir
print(s + str(3)) schreiben ist es eindeutig und wir erhalten
b3
als Ausgabe.
Die gängisten Möglichkeiten sind:
int(var) ..... Konvertiert var in eine Ganzzahl
float(var) ... Konvertiert var in eine Fließkommazahl
str(var) ..... Konvertiert Objekt var in eine String-Darstellung
repr(var) .... Konvertiert Objekt var in einen String-Ausdruck
eval(var) .... Wertet String var aus und gibt ein Objekt zurück
tuple(var) ... Konvertiert Sequenz var in ein Tupel
list(var) .... Konvertiert Sequenz var in eine Liste
chr(var) ..... Konvertiert die Ganzzahl var in ein Zeichen
ord(var) ..... Konvertiert das Zeichen in var in dessen Zahlwert
Also hier nochmals die zuvor genannten 3 bzw. 4 Varianten:
>>> s = "b"
>>> print(s + str(3))
b3
>>> print(int(s, 16) + 3)
14
>>> print(ord(s) + 3)
101
>>> print(chr(ord(s) + 3))
e
Durch die Angabe von
16 innerhalb der
int() Funktion als zweiten Parameter
wird Python mitgeteilt, dass mit der Basis
16 (hexadezimal) gearbeitet
wird. Ein
print(int("110", 2) + 3) würde dann
9 liefern weil
110 in binärer
Schreibweise die Zahl
6 ergibt wie wir schon bei den bitweisen Operatoren
festgestellt hatten.
Bei der Umwandlung von Kommazahlen zu Ganzzahlen werden die Kommastellen
direkt verworfen. Dies entspricht einem Abrunden.
>>> f = 3.8
>>> print(str(int(f)))
3
>>> print(str(int(f) + int(f)))
6
>>> print(str(int(f + f)))
7
Wir müssen also in solchen Fällen unbedingt darauf achten wann wir eine
Konvertierung durchführen. Vergleichen Sie dazu die Ergebnisse von Anweisung Nr. 3
und 4.
Wiederholungen & Verzweigungen
In quasi jedem Programm kommt es vor, dass auf bestimmte Umstände reagiert
werden muss, sei es eine Benutzereingabe oder das Auftreten eines
bestimmten Wertes in einer Datei die verarbeitet wird.
Darüber hinaus ist es auch sehr oft der Fall, dass bestimmte Schritte eines
Programmes mehrfach wiederholt werden müssen.
#!/usr/bin/python3
for i in range(1,5):
print(str(i)+": ", end="")
if i == 2:
print("i ist 2")
elif i > 2:
print("i ist größer als 2")
else:
print("nichts anderes trifft zu")
Schreiben Sie die oben aufgeführten Zeilen in einen Text-Editor Ihrer Wahl.
Sie können dazu zB IDLE verwenden indem Sie eine neue Datei erstellen oder
wenn Sie Komfortabel arbeiten wollen dann würde ich Ihnen Microsofts
Visual Studio Code empfehlen. Ich habe die Datei dann als
for_if.py abgespeichert
und kann das Script dann wie folgt aufrufen:
user@kali:~/PY_BUCH/000_Scripts/$ python3 for_if.py
1: nichts anderes trifft zu
2: i ist 2
3: i ist größer als 2
4: i ist größer als 2
Die ersten beiden Zeilen liefern die Zahlen 1 bis 4 am Anfang der Ausgabe.
Die
for-Schleife bietet sich an, wenn wir eine bestimmte Anzahl von
Wiederholungen durchführen wollen. Schleifen wiederholen einen Block von
Anweisungen entweder für eine vordefinierte Anzahl an Durchgängen oder bis
ein bestimmter Fall (zB Ende der Datei, alle Listenelemente durchalufen, usw.) eintritt.
Danach folgen die verschiedenen Verzweigungen. Die einfachste Variante wäre
ein
if-Block ohne
elif und ohne
else. Wenn die Bedingung vom
if-Block
zutrifft wird der eingerückte Anweisungsblock ausgeführt.
Falls nicht, werden der Reihe nach die Bedingungen des ersten, zweiten,
dritten, n-ten
elif-Blockes geprüft. Tifft eine der Bedingungen zu, dann
wird dieser Block ausgeführt. Wenn nicht, dann geht die Prüfung mit dem
nächsten
elif-Block weiter. Trifft keine einzige Bedingung zu dann werden
die Anweisungen des
else-Block ausgeführt (falls dieser vorhanden ist).
Außerdem ist
for die Schleife der Wahl um alle Elemente von Listen oder
Dictionaries zu durchlaufen. Zuerst erstelle ich die Datei
for_list.py mit
folgendem Inhalt:
#!/usr/bin/python3
l = ["DDD", "EEE", "FFF"]
for entry in l:
print(entry + " ", end="")
print("")
Dann können wir das Script wieder ausführen:
user@kali:~/PY_BUCH/000_Scripts/$ python3 for_list.py
DDD EEE FFF
Mit Hilfe von
for entry in d.keys() bekommen wir eine Liste der Schlüssel
eines Dictionaries und dann könnten wir mit dem Schlüssel auf den Wert des
Dictionary-Eintrags zugreifen. Alternativ können wir auch
for entry in
d.values() verwenden um nur die Werte ohne die Schlüssel zu bekommen.
Ein Dictionary biete außerdem die
items() Methode um komfortabel auf ein
Key : Value Paar zuzugreifen. Diese Methode liefert eine Liste von Tupeln,
die wir dann gleich im Schleifenkopf entpacken (auflösen zu einzelnen
Variablen - hierbei muss die Anzahl der Tupel-Elemente mit der Anzahl der
Variablen übereinstimmen).
#!/usr/bin/python3
d = {"a" : "AAA", "b" : "BBB", "c" : "CCC"}
for key, value in d.items():
print(key + " => " + value)
Bei der Ausführung erhalten wir dann:
user@kali:~/PY_BUCH/000_Scripts/$ python3 for_dict.py
a => AAA
b => BBB
c => CCC
Es gibt aber auch Fälle in denen wir die Anzahl der Wiederholungen nicht
kennen. Bespielsweise wenn User Daten eingeben oder Dateien durchlaufen
werden ist die Anzahl der Eingaben oder Zeilen in der Regel nicht bekannt.
Für solche Fälle gibt es die
while-Schleife.
#!/usr/bin/python3
import random
random.seed()
rand_number = random.randint(0, 9)
right_guess = False
while not right_guess:
guess = int(input("Zahl zwischen 0 und 9 eingeben: "))
if guess == rand_number:
right_guess = True
elif guess > rand_number:
print("Die gesuchte Zahl ist kleiner")
else:
print("Die gesuchte Zahl ist größer")
print("GEWONNEN! Die Zahl " + str(rand_number) + " wurde gesucht!")
Mit der
import-Anweisung laden wir zusätzliche Module in unser Programm. So
ein Modul stellt dann Klassen und Funktionen zur Verfügung. Was genau das
ist, erfahren wir später. Hier wird mit
import random die Funktionalität
zum Erzeugen von Zufallszahlen geladen.
Das
random.seed() sorgt dafür, dass der Zufallszahlengenerator bei jedem
Aufruf einen anderen Startwert bekommt. Damit ergibt sich auch immer eine
andere Reihenfolge der Zahlen. Danach wird mit
random.randint(0, 9) eine
Zufallszahl zwischen
0 und
9 generiert.
Mit
input(...) wird dann der Text ausgegeben und eine Benutzereingabe als
String eingelesen welcher dann gleich in einen
int-Wert konvertiert und der
Variable
guess zugewiesen wird.
Bei einer
while-Schleife ist zu bedenken, dass wir als Entwickler dafür
verantwortlich sind die Abburchbedingung irgendwann zu erreichen. Würde man
die Zeile
rightGuess = True entfernen, dann würde das Programm endlos
laufen ohne sich je zu beenden.
Dann spielen wir eine Runde und testen unser Programm:
user@kali:~/PY_BUCH/000_Scripts/$ python3 while_loop.py
Zahl zwischen 0 und 9 eingeben: 5
Die gesuchte Zahl ist größer
Zahl zwischen 0 und 9 eingeben: 8
Die gesuchte Zahl ist kleiner
Zahl zwischen 0 und 9 eingeben: 7
GEWONNEN! Die Zahl 7 wurde gesucht!
Kommentare
Ein umfangreicheres Programm oder Script kann schnell einige hundert bis zu
hunderttausenden Zeilen haben. Daher ist es wichtig sich selbst oder auch
anderen Programmierern Hinweise im Quellcode zu hinterlassen. Oftmals wird
dies auch am Beginn der Datei gemacht indem ein mehrzeiliger String dazu
zweckentfremdet wird. Kommentare beginnen mit dem
#-Zeichen und alles
dahinter ignoriert der Interpreter. Sehen wir uns als Beispiel folgendes
Programm an:
#!/usr/local/bin/python3
"""
Mein supertolles Programm zum errechnen des Quadrates einer Zahl
Systemvoraussetzung: Python Version 3.x
Lizenz: GPLv3
(c) Ich Selber 2018
"""
# Einlesen der Zahl
number = input("Zahl eingben: ")
# Komma in Punkt umwandeln
number = str(number).replace(",", ".")
# Prüfen ob die Eingabe eine Zahl ist
if number.isnumeric():
# Errechnen des Erg. und Ausgabe
res = float(number) ** 2
print("Das Quadrat von " + str(number), end="") # end = "" nur
print(" ist: " + str(res)) # damit es unter Python 2.x nicht läuft :P
else:
# Fehler ausgeben
print("Ihre Eingabe ist keine gültige Zahl!")
In der ersten Zeile wird das Pseudo-Kommentar für Unix- und Linux-Systeme
angegeben. Dort kann man eine einfache Textdatei als ausführbar markieren
und dann wird anhand dieses Pseudokommentares der Interpreter bestimmt.
Danach kommt unser zweckentfremdeter mehrzeiliger String in dem diverse
Infos zum Programm, Systemvoraussetzungen, Lizenz, Danksagungen, beteiligte
Entwickler uvm. untergebracht werden können. Danach folgen einige
sogenannte einzeilige Kommentare, die mit dem
#-Zeichen eingeleitet werden.
Diese Kommentare können sowohl über einer Programmzeile als auch am rechten
Ende der Zeile stehen.
Funktionen
Wir haben bisher schon des Öfteren die Begriffe Methoden und Funktionen
gehört. Daher werden wir zunächst einmal klären was eine Funktion überhaupt
ist.
Unter einer Funktion verstehen wir einen in sich abgeschlossenen
Code-Block. Dies wird verwendet um wiederkehrende Aufgaben vom
Hauptprogramm abzuspalten und zentral zu verwalten. Außerdem wird der Code
einfacher zu warten da so wiederkehrende Blöcke zentral in einer Funktion
stehen und damit an einer zentralen Stelle veränderbar sind.
Sehen wir uns einfach ein Beispiel an:
#!/usr/local/bin/python3
PI = 3.14
def circle_area(r):
area = r ** 2 * PI
return area
print(circle_area(2))
print(circle_area(4))
print(circle_area(8))
print(area)
Dann lassen wir das Script laufen und sehen uns an was wir erhalten:
user@kali:~/PY_BUCH/000_Scripts/$ python3 circle_area_func.py
12.56
50.24
200.96
Traceback (most recent call last):
File "/Users/mac/Buch_Hacken_mit_Python/00_Scripts/00_while.py", line 12,
in
print(area)
NameError: name 'area' is not defined
Hier sehen wir beide Methoden um Daten an eine Funktion zu übergeben - den
Funktionsparameter (
r) der beim Aufruf der Funktion übergeben wird und die
Variable
PI, die außerhalb der Funktion definiert ist. Hierbei gilt das die
Funktion auf Variablen von draußen zugreifen kann der Zugriff von Außen auf
eine Funktionsvariable ist jedoch nicht erlaubt.
Darum gibt es auch das
return-Schlüsselwort mit dem definiert wird welcher
Wert von der Funktion wieder zurückgegeben wird. So bekommen wir Werte
wieder aus der Funktion heraus. Hierbei können wir aber nur eine einzige
Variable zurückgeben.
Sollen mehrere Werte zurückgeliefert werden müssen wir eine Liste, ein
Dictionary oder einen Tupel mit den benötigten Werten zurückgeben. Ein
Beispiel hierfür wäre die
items() Methode von Dictionaries, die zB eine
Liste von Tupeln liefert.
Dies wäre aber keine Regel gäbe es nicht auch eine Ausnahme:
Übergeben wir einer Funktion eine Liste, ein Dictionary oder einen Tuple
dann ändert sich dieses Verhalten! In so einem Fall wird keine Kopie des
Wertes übergeben sondern eine sogenannte
Referenz auf das Objekt. Das kann man sich wie eine Verknüpfung unter
Windows oder einen Link unter Unix/Linux vorstellen.
Die Referenz ist einfach ein Zeiger auf den Bereich im
RAM-Speicher in dem
die Original-Daten liegen. Hier würden also die Originaldaten verändert.
Man kann Python allerdings zwingen die Daten zu kopieren indem man statt
einer Liste ein Slice mit allen Werten (
liste[:]) übergibt. Je nach Menge
der Einträge kann die Performance des Programms darunter spürbar leiden.
Arbeiten mit Dateien
Bisher habe wir nur sehr einfache Programme geschrieben aber selbst bei dem
Ratespielchen wäre es schon sinnvoll gewesen den Highscore in einer Liste
abzuspeichern.
Die Variablen, die ihre Daten im
RAM-Speicher ablegen sind flüchtig. Wird
das Programm beendet wird dieser Speicher wieder freigegeben und die Daten
sind verloren. Wollen wir Daten dauerhaft ablegen dann müssen wir diese auf
die Festplatte oder in eine Datenbank schreiben.
with open("daten.txt", "w") as file:
file.write("bla")
file.write("blub")
file.write("foo")
Hierbei sorgt das
with open("daten.txt", "w") as file dafür, dass die
Datei nach dem Beenden des Blockes auch wieder sauber geschlossen wird. Das
hat den Vorteil, dass wir das nicht vergessen können.
Alternativ können wir auch
file = open("daten.txt", "w") schreiben. Danach
müsste man die
write-Befehle nicht einrücken aber dafür die Datei mit
file.close() wieder schließen wenn wir fertig sind.
Der
open-Befehl benötigt zwei Parameter - den Dateinamen inkl. Pfad bzw.
falls wir keinen Pfad angeben sucht Python automatisch im gleichen Ordner
wie das Script und den Modus. Für den Modus gibt es folgende Optionen:
a .... Anfügen von Daten am Ende der Datei (append)
r .... Lesen (read)
w .... Schreiben bzw. Überschreiben (write)
ab ... Anfügen im binären Modus (append binary)
rb ... Lesen im binären Modus (read binary)
wb ... Schreiben bzw. Überschreiben im binären Modus (write binary)
Wird eine bereits vorhandene Datei zum Schreiben geöffnet wird der alte
Inhalt vollständig gelöscht und ersetzt. Selbst wenn der neue Inhalt kürzer
ist gehen alle vorherigen Zeilen des Inhaltes verloren. Dies geschieht auch
ohne jegliche Sicherheitsfrage wenn wir diese nicht selbst programmieren.
Daher sollte man ein solches Programm ausführlich mit Dummy-Dateien testen
bevor man es auf wichtige Systemdateien oder ähnliches loslässt.
Wenn wir die Datei in einem Editor öffnen erhalten wir folgenden Inhalt:
blablubfoo
So war das eigentlich nicht geplant. Wir müssen also beim Schreiben der
Datei zusätzlich einen Zeilenumbruch an die Daten anfügen, wenn wir mehrere
Zeilen schreiben wollen. Das erreichen wir beispielsweise mit
file.write("bla\n").
Wenn wir Überprüfen wollen, ob eine Datei oder ein Verzeichnis existiert
bzw. ob wir schreibend auf eine Datei zugreifen dürfen, dann haben wir
folgende Optionen:
import os
>>> print(os.path.exists("/bin"))
True
>>> print(os.path.isdir("/bin"))
True
>>> print(os.path.isfile("/bin"))
False
>>> print(os.path.exists("/bin/sh"))
True
>>> print(os.path.isdir("/bin/sh"))
False
>>> print(os.path.isfile("/bin/sh"))
True
>>> print(os.access("/bin/sh", os.W_OK))
False
>>> print(os.access("/bin/sh", os.R_OK))
True
>>> print(os.access("/bin/sh", os.X_OK))
True
Die Methode
exists() überprüft nicht, ob es sich um eine Datei oder einen
Ordner handelt sondern lediglich ob der Pfad existiert. Mit
isfile() und
isdir() lässt sich feststellen ob es sich um eine Datei oder einen Ordner
handelt.
os.access() überprüft ob auf eine Datei oder einen Ordner in einem
bestimmten Modus zugegriffen werden kann. Der erste Parameter ist der Pfad
und der zweite Parameter der Modus. Hierbei prüft
os.W_OK ob Schreibzugriff
erlaubt ist,
os.R_OK ob Lesezugriff erlaubt ist und
os.X_OK ob die Datei ausführbar ist.
Achtung!!!
Unter Linux und Unix müssen Ordner ebenfalls ausführbar sein um geöffnet zu
werden.
Danach können wir die Datei mit folgendem Code wieder einlesen:
with open("daten.txt", "r") as file:
for line in file:
print(line.rstrip())
Jetzt erhalten wir als Ausgabe:
bla
blub
foo
Da die
print() Funktion selbst einen Zeilenumbruch anfügt und beim lesen
der Zeile der Zeilenumbruch ebenfalls als Teil der Zeile mitgeliefert wird
benötigen wir ein
rstrip() um die sogennannten Whitespaces (Leerzeichen,
Tabulatoren, Zeilenschaltungen, etc.) am rechten Ende der Zeile zu
entfernen. Andernfalls wäre bei der Ausgabe zwischen den Zeilen immer eine
Leerzeile.
Für die Arbeit mit XML-Dateien kann ich Ihnen das Modul
xmltodict
empfehlen. Wie der Name vermuten lässt sorgt dieses Modul dafür, dass aus
den XML-Daten ein Python-Dictionary wird mit dem Sie dann wie bereits
erklärt arbeiten können.
Objektorientierte Programmierung
Objektorientierte Programmierung (OOP) ermöglicht es zusammengehörige Logik
in einer sogenannten Klasse zu bündeln. Außerdem kann diese Klasse private
und öffentliche Eigenschaften (Daten) und Methoden (Funktionen) besitzen.
Damit ist es nicht nur möglich logische Einheiten zu bilden und so für mehr
Struktur und Ordnung zu sorgen, sondern auch festzulegen welche Methoden
und Eigenschaften dem Nutzer der Klasse zugänglich sein sollen und welche
nur intern verwendet werden.
Vererbung ist ein weiteres wichtiges Konzept von OOP. Im Grunde ist es
recht einfach - stammt eine Klasse von einer anderen Klasse ab dann werden
die Eigenschaften und Methoden der Eltern-Klasse übernommen.
In der neuen Klasse können die übernommenen Eigenschaften und Methoden dann
erweitert und/oder komplett überschreiben werden. Damit das Ganze etwas
klarer wird sehen wir uns ein kleines Beispiel an...
#!/usr/local/bin/python3
class Vehicle():
def __init__(self, model, manufacturer, price):
self.__model = model
self.__manufacturer = manufacturer
self.__price = price
def showInfo(self):
return self.__manufacturer + " " + self.__model
def getPrice(self):
return self.__price
Soweit sollte alles klar sein. Hier legen wir eine Basis-Klasse an die sehr
allgemein gehalten ist und ein Fahrzeug mit den Eigenschaften Modellname,
Hersteller und Preis beschreibt. Die Methoden
showInfo() und
getPrice()
liefern die Daten zurück.
Um den Code knapper und übersichtlicher zu halten habe ich keine Methoden
zum Ändern der Daten implementiert.
class MotorBike(Vehicle):
def __init__(self, model, manufacturer, price, hp, year, km):
super().__init__(model, manufacturer, price)
self.__hp = hp
self.__year = year
self.__km = km
def showInfo(self):
return super().showInfo() + ", " + str(self.__hp) + \
" PS, EZ " + str(self.__year) + ", " + str(self.__km) + " km"
MotorBike, die zweite Klasse unseres Beispiels stammt von der Klasse
Vehicle ab. Das wird durch die Schreibweise
class KlassenName(ElternKlasse)
erreicht. Das Klassennamen mit Großbuchstaben anfangen hat sich so
eingebürgert und dient der Abgrenzung von den Variablennamen, die nach
dieser Konvention mit einem Kleinbuchstaben beginnen.
In der
__init__() Methode wird zuerst
super().__init__(model, manufacturer,
price) aufgerufen. Damit wird der Konstruktor der Elternklasse ausgeführt
welcher die von dort geerbten Variablen model, manufacturer und price
belegt. Da diese Variablen vererbt wurden müssen Sie nicht von Hand in der
abgeleiteten Klasse angelegt werden. Genauso erlaubt der Aufruf von
super().__init__(...) es den Konstruktor der Elternklasse in der aktuellen
Klasse auszuführen und damit die drei Eigenschaften in dieser Klasse zu
belegen.
Mit
self.__hp = hp und den zwei folgenden Zeilen werden PS, EZ und
gefahrene km als weitere Eigenschaften der Klasse definiert.
Bei der Ausgabe der Daten verfahren wir ähnlich. Zuerst holen wir die
Ausgabe der Elternklasse mit
super().showInfo() und ergänzen diese mit
+ ",
" + str(self.__hp) + ... um die zusätzlichen Felder, die nur in dieser
Klasse existieren.
class Car(MotorBike):
def __init__(self, model, manufacturer, price, hp, year, km, doors):
super().__init__(model, manufacturer, price, hp, year, km)
self.__doors = doors
def showInfo(self):
return super().showInfo() + ", " + str(self.__doors) + "-türig"
Die Definition der
Car-Klasse ist noch kürzer. Hier leiten wir die Klasse
von der
MotorBike-Klasse ab da diese so gut wie alle benötigten
Eigenschaften dort bereits implementiert sind.
Das Vorgehen ist hierbei absolut das gleiche - wir rufen mit
super()... die
Funktionaliät aus der Elternklasse
MotorBike auf und erweitern diese um die
Eigenschaft
__doors für die Türanzahl.
class Quad(MotorBike):
pass
Die einfachste Klassendefinition ist unsere
Quad-Klasse. Hier erstellen wir
nichts weiter als eine eins zu eins Kopie der Klasse MotorBike unter einem
neuen Namen. Die Anweisung
pass wird benötigt da wir in Python keine
End-Markierungen für Blöcke haben. Somit sagt das
pass in dem Fall nur,
dass der leere Block so gewollt ist.
Damit ist das Anlegen der Klassen fertig und wir können nun folgende
Objekte erstellen:
v = Vehicle("City Fun 28.3", "KTM", 429.00)
b = MotorBike("CBR 125R", "Honda", 6990.00, 14, "05/2013", 6788)
c = Car("Fabia 1.9 TDI Kombi", "Skoda", 12990.00, 101, "09/2014", 37855, 5)
q = Quad("King 750 AXI", "Suzuki", 4990.00, 38, "04/2014", 7985)
Im Grunde ist das nichts anderes als 4 Variablen anzulegen und mit den
jeweils geforderten werten zu füttern.
for obj in [v, b, c, q]:
print(obj.showInfo(), end="")
print(", VKP %.2f EUR" % obj.getPrice())
Danach durchlaufen wir die 4 Variablen mit einer
for-Schleife. Hier wird
Ihnen vielleicht auffallen, dass die Klassen
Quad und
Car beide von
MotorBike abstammen und dass selbst in
MotorBike keine Methode getPrice()
angelegt wurde.
Diese Methode erbt
MotorBike selbst von
Vehicle und vererbt die Methode
weiter an seine Kind-Klassen. Wenn wir so eine Klassenhirarchie aufbauen
dann können wir den geschriebenen Code massiv verkürzen und die
Wartungsfreundlichkeit erhöhen.
Natürlich macht es keinen Sinn mit Klassen und abgeleiteten Klassen zu
arbeiten um ein kleines Wartungsscript mit wenigen Zeilen zu realisieren.
Wenn wir aber an größeren Projekten arbeiten ist OOP der beste Weg um kompakten Code zu schreiben den wir auch gut für
andere Projekte wiederverwenden können.
Lassen wir unser Programm laufen erhalten wir folgendes:
KTM City Fun 28.3, VKP 429.00 EUR
Honda CBR 125R, 14 PS, EZ 05/2013, 6788 km, VKP 6990.00 EUR
Skoda Fabia 1.9 TDI Kombi, 101 PS, EZ 09/2014, 37855 km, 5-türig, VKP 12990.00 EUR
Suzuki King 750 AXI, 38 PS, EZ 04/2014, 7985 km, VKP 4990.00 EUR
Wir erhalten für jedes Objekt die passende Ausgabe. Genau das was wir
wollten - im Grunde können wir vier verschiedene Objekte mit der gleichen
Methode behandeln und erhalten dennoch immer die passende Ausgabe mit den
kleinen Unterschieden.
Wenn wir genau nachvollziehen wollen wie sich so ein Objekt zusammensetzt
dann können wir den Aufbau mit
print(c.__dict__)
ausgeben lassen. Bei unserem Auto erhalten wir beispielsweise:
{
'_Vehicle__model': 'Fabia 1.9 TDI Kombi',
'_Vehicle__manufacturer': 'Skoda',
'_Vehicle__price': 12990.0,
'_MotorBike__hp': 101,
'_MotorBike__year': '09/2014',
'_MotorBike__km': 37855,
'_Car__doors': 5
}
Fehlerbehandlung
Selbst wenn wir versuchen mögliche Fehler zu bedenken - in vielen anderen
Fällen steigt die Anzahl möglicher Fehler extrem an und wir werden
schwerlich jeden dieser Fehler im Vorfeld bedenken können.
Nehmen wir dazu einfach das Laden von Daten aus dem Internet: Der Server
kann nicht erreichbar sein, die Verbindung kann während der Übertragung
abreißen, die Datei kann gelöscht worden sein, der Zugriff auf die Datei
wird verweigert wegen unzureichender Rechte, der Download kann erfolgen
aber die Datei beim Transport beschädigt werden, die Datei kann
überschrieben worden sein und nun einen ganz anderen Inhalt haben als unser
Programm erwartet, uvm.
Sie sehen wir haben nicht nur eine umfangreiche Liste an möglichen Fehlern,
sondern einige der Fehler kann man vorab gar nicht ausschließen. So kann
man vorab unmöglich ausschließen, dass eine Datei beim Download beschädigt
wird und nach dem Download zu testen, ob die Datei intakt ist gestaltet
sich oft äußerst schwierig wenn man keine MD5 oder SHA Checksumme hat.
Daher ist es in vielen Fällen effizienter Fehler abzufangen und darauf zu
reagieren. Vor allem stellt es sicher, dass wir keinen möglichen Fehler
vergessen haben. Und so können wir das in Python realisieren:
#!/usr/local/bin/python3
try:
print("Try-Block Beginn")
with open("xxx", "r") as file:
print(file)
for line in lines:
print(30 / int(line.strip()))
print("Weitere Anweisungen im Try-Block")
except FileNotFoundError:
print ("Kann xxx nicht finden")
except:
print ("Unerwarteter Fehler aufgetreten")
print("Weitere Anweisungen nach dem Try/Except-Block")
Sehen wir uns einmal die Ausgabe des Scriptes an:
Try-Block Beginn
Kann xxx nicht finden
Weitere Anweisungen nach dem Try/Except-Block
Wir sehen den Beginn des
try-Blocks, da die Datei
xxx nicht existiert tritt
ein Fehler im
with-Block auf und der ganze
try-Block wird abgebrochen. So
wird auch der
print-Befehl "Weitere Anweisungen im Try-Block" nach dem
with-block übersprungen.
Daher bietet es sich an im
try-Block allen Code unterzubringen der davon
abhängt, dass das Lesen der Datei klappt.
Tritt ein Fehler auf wird der passende
except-Block ausgeführt. In unserem
Fall ist hier ein
FileNotFoundError aufgetreten und daher wird auch dieser Block
abgearbeitet. Um zu zeigen was passiert wenn es klappt, legen wir die Datei
xxx mit einer Zeile in der
10 steht an.
user@kali:~/PY_BUCH/000_Scripts/$ echo "10" > xxx
Danach können wir das Script wieder laufen lassen:
Try-Block beginn
<_io.TextIOWrapper name='xxx' mode='r' encoding='UTF-8'>
3.0
Weitere Anweisungen im Try-Block
Weitere Anweisungen nach dem Try/Except-Block
Alle Programmanweisungen wurden wie gewünscht durchlaufen. Zuerst wird der
Dateihandler ausgegeben und danach wir das Resultat der Berechnung 30 / 10
ausgegeben.
Um zu sehen wozu wir den zweiten except-Block brauche werden wir einen
weiteren Fehler erzeugen. Dazu werden wir eine weitere Zeile hinzufügen,
die die Zahl
0 enthält. So simulieren wir, dass in einer Datei unerwartete
Einträge enthalten sind. Dadurch wird ein anderer Fehler
(
ZeroDivisionError) auftreten.
user@kali:~/PY_BUCH/000_Scripts/$ echo "0" >> xxx
Und wir lassen das Script wieder laufen:
Try-Block beginn
<_io.TextIOWrapper name='xxx' mode='r' encoding='UTF-8'>
3.0
Unerwarteter Fehler aufgetreten
Weitere Anweisungen nach dem Try/Except-Block
Die Zeile Weitere Anweisungen im
try-Block wird übersprungen, da bei der
Berechnung von 30 / 0 ein Fehler auftritt. Da wir nicht explizit einen
except-Block für den
ZeroDivisionError angelegt haben, wird der
except-Block ohne spezifizierten Fehler ausgeführt.
Natürlich ist der Hinweiß auf einen nicht erwarteten Fehler wenig hilfreich
für den User aber immer noch besser als ein Programmabsturz mit
Datenverlust. Es wäre genausogut denkbar gewesen , dass Sie keine
Berechtigung haben auf die Datei zuzugreifen. Dies kann dadurch geschehen,
dass bereits jemand anders mit der Datei arbeitet oder ein Administrator
Ihnen den Zugriff auf diese Datei entzogen hat.