Notes 6
Sie können diese Seite ausdrucken oder mit einem PDF-Drucker in ein PDF umwandeln, um Ihre eigenen Notizen hinzuzufügen.
Willkommen!
- In den vergangenen Wochen haben Sie grundlegende Bausteine der Programmierung kennengelernt.
- Sie lernten die Low-Level-Programmiersprache C kennen.
- Heute werden wir mit einer höheren Programmiersprache namens Python arbeiten.
- Während Sie diese neue Sprache lernen, werden Sie feststellen, dass Sie besser in der Lage sind, sich selbst neue Programmiersprachen beizubringen.
Der Python-Moment
Erinnern Sie sich an die Filter-Aufgabe? Box-Blur, Edge-Detection – Pixel für Pixel durchgehen, Nachbarn berechnen, Speicher verwalten…
In Python sieht das so aus:
from PIL import Image, ImageFilter before = Image.open("bridge.bmp") after = before.filter(ImageFilter.BoxBlur(10)) after.save("out.bmp")Das ist alles. Vier Zeilen.
- Wie ist das möglich? Python ist eine höhere Programmiersprache, die viele der mühsamen Details für uns übernimmt. In den nächsten 90 Minuten werden Sie verstehen, warum.
Python
- Die Menschen haben im Laufe der Jahrzehnte erkannt, wie frühere Designentscheidungen verbessert werden können.
- Python ist eine Programmiersprache, die auf dem aufbaut, was Sie bereits in C gelernt haben.
- Anders als C ist Python eine interpretierte Sprache, bei der Sie Ihr Programm nicht separat kompilieren müssen. Stattdessen führen Sie Ihr Programm im Python Interpreter aus.
- Schauen Sie sich das Short-Video zu Python-Grundlagen an, das die wichtigsten Unterschiede zwischen C und Python erklärt.
Hallo
Bis zu diesem Punkt sah der Code in etwa wie folgt aus:
// Ein Programm, das der Welt "Hallo" sagt #include <stdio.h> int main(void) { printf("Hallo, Welt"); }Heute ist das Programmieren einfacher.
Zum Beispiel wird der obige Code in Python wie folgt dargestellt:
# Ein Programm, das der Welt Hallo sagt print("Hallo, Welt")Beachten Sie, dass das Semikolon wegfällt und dass keine Bibliothek benötigt wird.
Python kann insbesondere das, was in C recht kompliziert war, relativ einfach umsetzen.
Filter (Rückblick)
- Sie haben am Anfang der Vorlesung gesehen, wie wenig Code für einen Blur-Filter in Python nötig ist.
- Das
PIL-Modul (Python Imaging Library) stelltImageundImageFilterzur Verfügung, mit denen Sie Bilder laden, filtern und speichern können. - Mit Python können Sie bei der Programmierung noch mehr von den Details abstrahieren als in C und anderen niedrigeren Programmiersprachen.
CS50-Bibliothek
Wie in C kann auch in Python eine cs50-Bibliothek verwendet werden, um den Einstieg zu erleichtern.
Die folgenden Funktionen sollten Ihnen bekannt vorkommen:
get_float get_int get_stringAnders als in C können Sie in Python nur die tatsächlich benötigten Funktionen aus der CS50-Bibliothek importieren:
from cs50 import get_float, get_int, get_string
Strings
In C erinnern Sie sich vielleicht an diesen Code:
// get_string und printf mit %s #include <cs50.h> #include <stdio.h> int main(void) { string answer = get_string("Wie ist Ihr Name?"); printf("Hallo, %s\n", answer); }Dieser Code sieht in Python so aus:
# get_string und print, mit Format-Strings (f-Strings) from cs50 import get_string answer = get_string("Wie ist Ihr Name?") print(f"hallo, {answer}")Beachten Sie, wie die geschweiften Klammern es der Funktion
printermöglichen, dassanswerdarin erscheint. Dasfist erforderlich, umanswerim String richtig zu formatieren (sog. f-String). Das funktioniert nicht nur in print, sondern überall, wo Strings verwendet werden.Ohne CS50-Stützräder:
answer = input("Wie ist Ihr Name? ") print(f"hallo, {answer}")
Variablen
- Auch die Variablendeklaration wird vereinfacht. In C könnten Sie
int counter = 0;angeben. In Python würde dieselbe Zeile lauten:counter = 0. Sie müssen den Typ der Variablen nicht angeben, weil Python eine dynamisch typisierte Sprache ist (C ist statisch typisiert). - Python nutzt
counter += 1, um um eins zu erhöhen. Die aus C bekannte Möglichkeit mitcounter++gibt es in Python nicht.
PEP 8 – Python Style Guide
- PEP 8 ist der offizielle Style Guide für Python-Code. Er sorgt dafür, dass Python-Code einheitlich und gut lesbar ist.
- Die wichtigsten Regeln:
- Einrückungen mit 4 Leerzeichen (keine Tabs)
- Leerzeichen vor und nach Operatoren:
a = b + cstatta=b+c - Aussagekräftige Variablennamen:
anzahl_studentenstattx - Maximale Zeilenlänge: 79 Zeichen
- Tools wie
autopep8können Code automatisch formatieren:autopep8 --in-place meine_datei.py - Mehr dazu in der offiziellen PEP 8 Dokumentation.
Typen
Datentypen müssen in Python nicht explizit deklariert werden. Sie haben zum Beispiel oben gesehen, dass
Antworteine Zeichenkette ist, aber wir mussten dem Interpreter nicht sagen, dass dies der Fall ist: Er wusste es von selbst; den Datentyp hat er sich selbst erschlossen.In Python werden häufig folgende Typen verwendet:
bool float int strBeachten Sie, dass
longunddoublefehlen. Python nutzt für größere Zahlen automatisch Repräsentationen mit ausreichender Genauigkeit.Sie können das selbst ausprobieren, indem Sie den Python-Interpreter in einem Terminal durch Eingabe von
python(oderpython3) im interaktiven Modus starten und folgendes eingeben:10**100– das Ergebnis dieser Potenzierung von 10 mit der Zahl 100 ist eine Zahl mit 101 Stellen – und wäre in C nicht ohne weiteres in einer Variablen speicherbar.Einige andere Datentypen in Python sind:
range (Sequenz von Zahlen) list (Listen, deren Größe automatisch angepasst wird) tuple (Tupel, also nicht veränderbare Listen) dict (Dictionaries mit Key-Value-Paaren) set (Mengen ohne Duplikate)Jeder dieser Datentypen kann auch in C implementiert werden, aber in Python können sie einfacher implementiert werden.
Rechner
Vielleicht erinnern Sie sich an
calculator.cvon früher im Kurs:// Addition mit int #include <cs50.h> #include <stdio.h> int main(void) { // Aufforderung an den Benutzer zur Eingabe von x int x = get_int("x: "); // Aufforderung an den Benutzer zur Eingabe von y int y = get_int("y: "); // Addition durchführen printf("%i\n", x + y); }Wir können einen einfachen Taschenrechner genauso implementieren, wie wir es in C getan haben. Geben Sie
code calculator.pyin das Terminalfenster ein und schreiben Sie folgenden Code:# Addition mit int [mittels get_int] from cs50 import get_int # Benutzer zur Eingabe von x auffordern x = get_int("x: ") # Benutzer zur Eingabe von y auffordern y = get_int("y: ") # Addition durchführen print(x + y)Beachten Sie, wie die CS50-Bibliothek importiert wird. Dann werden “x” und “y” vom Benutzer abgefragt. Schließlich wird das Ergebnis ausgegeben. Beachten Sie, dass die Funktion “main”, die in einem C-Programm zu sehen wäre, völlig fehlt! Obwohl man eine “main”-Funktion verwenden könnte, ist sie nicht erforderlich.
Wir versuchen auch hier wieder, die Stützräder der CS50-Bibliothek zu entfernen. Ändern Sie Ihren Code wie folgt:
# Addition mit int [mit input] # Benutzer zur Eingabe von x auffordern x = input("x: ") # Benutzer zur Eingabe von y auffordern y = input("y: ") # Addition durchführen print(x + y)Beachten Sie, dass die Ausführung des obigen Codes zu einem sehr seltsamen Programmverhalten führt. Woran mag das liegen?
Wenn Sie an dieser Stelle vermutet haben, dass der Python-Interpreter
xundyals Strings betrachtet und daher die Konkatenation vonxundyausgibt, dann liegen Sie damit richtig. Sie können den Code korrigieren, indem Sie die Funktionintzur Typumwandlung wie folgt verwenden:# Addition mit int [mit Eingabe] # Benutzer zur Eingabe von x auffordern x = int(input("x: ")) # Benutzer zur Eingabe von y auffordern y = int(input("y: ")) # Addition durchführen print(x + y)Beachten Sie, wie die Eingabe für
xundyan die Funktionintübergeben wird, die sie in eine Ganzzahl umwandelt. Ohne die Umwandlung von “x” und “y” in Ganzzahlen werden die Zeichen aneinandergereiht (konkateniert).
Bedingte Anweisungen
Aus unseren C-Vorlesungen kommt Ihnen vielleicht ein Programm wie dieses vertraut vor
// Bedingte Anweisungen, boolesche Ausdrücke, relationale Operatoren #include <cs50.h> #include <stdio.h> int main(void) { // Aufforderung zur Eingabe von Ganzzahlen int x = get_int("Wie groß ist x? "); int y = get_int("Wie lautet y? "); // Ganzzahlen vergleichen if (x < y) { printf("x ist kleiner als y\n"); } else if (x > y) { printf("x ist größer als y\n"); } else { printf("x ist gleich y\n"); } }In Python würde das wie folgt aussehen:
# Bedingte Anweisungen, boolesche Ausdrücke, relationale Operatoren from cs50 import get_int # Benutzer zur Eingabe von Ganzzahlen auffordern x = get_int("Wie groß ist x? ") y = get_int("Wie lautet y? ") # Ganzzahlen vergleichen if x < y: print("x ist kleiner als y") elif x > y: print("x ist größer als y") else: print("x ist gleich y")Beachten Sie, dass es, erstens, keine geschweiften Klammern mehr gibt. Stattdessen werden Einrückungen verwendet. Zweitens wird ein Doppelpunkt in der “if”-Anweisung verwendet. Außerdem wird
else ifdurchelifersetzt. Auch Klammern sind in den Anweisungenifundelifnicht mehr erforderlich.
Truthiness: Was ist wahr und was ist falsch?
In Python gelten viele Werte als “falsch” (falsy), nicht nur
Falseund0:# Diese Werte sind alle "falsy": x = 0 # False x = "" # False (leerer String) x = [] # False (leere Liste) x = None # False (Pythons NULL) # Diese Werte sind "truthy": name = "Max" # True (nicht-leerer String) zahl = 42 # True (nicht-null)Das ermöglicht elegante Prüfungen:
name = input("Name: ") if name: # statt: if name != "" print(f"Hallo, {name}") else: print("Kein Name eingegeben")
Verkettete Vergleiche
- Python erlaubt elegante verkettete Vergleiche:
# Statt: if alter >= 13 and alter <= 19 if 13 <= alter <= 19: print("Teenager")
Ternärer Operator
Python hat einen kompakten bedingten Ausdruck:
# Statt if/else über mehrere Zeilen: status = "erwachsen" if alter >= 18 else "minderjährig"Anders als in C lassen sich Strings genauso einfach vergleichen:
# Benutzer zur Eingabe von Strings auffordern s = input("s? ") t = input("t? ") if s == t: print("same") else: print("different")
Gleichheit vs. Identität: == und is
==prüft, ob zwei Werte gleich sind (Inhalt).isprüft, ob zwei Variablen auf dasselbe Objekt zeigen (Identität).x = [1, 2, 3] y = [1, 2, 3] print(x == y) # True - gleicher Inhalt print(x is y) # False - verschiedene ObjekteFür
Nonesollte man immerisverwenden:x = None if x is None: # Richtig! print("Ist None") # if x == None: # Funktioniert, aber nicht idiomatischBetrachten Sie den bereits bekannten C-Code, um weitere Vergleiche anzustellen:
// Logische Operatoren #include <cs50.h> #include <stdio.h> int main(void) { // Aufforderung an den Benutzer, zuzustimmen char c = get_char("Sind Sie einverstanden?"); // Prüfen, ob einverstanden if (c == 'Y' || c == 'y') { printf("Einverstanden.\n"); } else if (c == 'N' || c == 'n') { printf("Nicht einverstanden.\n"); } }Dies kann in Python wie folgt umgesetzt werden:
# Logische Operatoren from cs50 import get_string # Aufforderung an den Benutzer zuzustimmen s = get_string("Sind Sie einverstanden?") # Prüfen, ob einverstanden if s == "Y" or s == "y": print("Einverstanden.") elif s == "N" or s == "n": print("Nicht einverstanden.")Beachten Sie, dass die beiden Senkrechtstriche, die in C verwendet werden, durch “or” ersetzt werden. In der Tat bevorzugen viele Menschen Python auch deswegen, weil es für Menschen besser lesbar ist. Beachten Sie auch, dass es in Python kein
chargibt. Stattdessen wird auch für einzelne Zeichen der Datentyp “str” verwendet; die in C erforderliche Unterscheidung (einfache oder doppelte Anführungszeichen) gibt es hier nicht.Eine “pythonischere” (more pythonic) Herangehensweise an das Problem würde wie folgt aussehen.
Was bedeutet “pythonic”? In der Python-Community gibt es oft mehrere Wege, ein Problem zu lösen. Als “pythonic” bezeichnet man Code, der die Stärken und Idiome der Sprache nutzt – also lesbarer, eleganter und dem “Python-Geist” entsprechender Code. Es gibt sogar einen eingebauten Leitfaden: Tippen Sie
import thisin den Python-Interpreter!Hier wird der Datentyp Liste verwendet (Notation in eckigen Klammern):
# Logische Operatoren, die Listen verwenden from cs50 import get_string # Aufforderung an den Benutzer zuzustimmen s = get_string("Sind Sie einverstanden?") # Prüfen, ob einverstanden if s in ["y", "yes"]: print("Einverstanden.") elif s in ["n", "nein"]: print("Nicht einverstanden.")Beachten Sie, dass wir mehrere Schlüsselwörter wie “y” und “ja” in einer “Liste” ausdrücken können.
Etwas unelegant ist, dass wir für alle Varianten von Groß- und Kleinschreibung nun entsprechende Elemente in den Listen benötigen. In C würden wir die Zeichen in
smit tolower(…) aus der Bibliothekctype.hin Kleinbuchstaben umwandeln (Zeichen für Zeichen in einer Schleife).Schauen Sie sich das Short-Video zu Kontrollfluss und Strings für weitere Details an.
Objektorientierte Programmierung
Unter Designgesichtspunkten ist das Anbieten einer Funktion wie tolower(…) nicht besonders elegant. Nach dem Include steht sie im Programm einfach so zur Verfügung wie jede andere Funktion. Bei vielen Includes wird es schnell unübersichtlich und es könnte sogar zu Namenskonflikten kommen, wenn z.B. mehrere Bibliotheken gleichzeitig included werden, die die gleichen Funktionen anbieten. Bisher haben wir das so hingenommen.
Wäre es nicht schöner, wenn die Funktionen, die bestimmte Daten verarbeiten, näher bei den Daten definiert wären, auf die sie anwendbar sind?
In der Realität sind wir es gewohnt, dass Objekte zum einen Eigenschaften mit sich bringen und zum anderen bestimmtes Verhalten haben. Ein Auto hat vier Räder und eine bestimmte Farbe. Es kann beschleunigen und bremsen.
Es bietet sich an, dieses Konzept auch bei der Programmierung zu verwenden und dadurch den Code besser zu organisieren.
Das ist die Idee der objektorientierten Programmierung, in der wir nicht mit unstrukturierten Daten arbeiten, sondern mit Objekten.
Mit C können wir das nicht umsetzen. Wir können lediglich eine struct erstellen, um mehrere Variablen innerhalb eines einzigen, selbst erstellten Datentyps miteinander zu verbinden. In Python können wir hingegen objektorientiert programmieren – und haben das von Anfang an getan, weil in Python alles ein Objekt ist!
Beim objektorientierten Programmieren können wir nicht nur mehrere Werte zu einem Objekt zusammenfassen, sondern auch Funktionen auf den Objekten aufrufen, die zu Objekten von einem bestimmten Typ (einer Klasse) gehören. Solche Funktionen nennen wir Methoden. Methoden werden durch den Punktoperator auf ein Objekt angewendet.
Zum Beispiel haben str-Objekte in Python eine eingebaute Methode, mit denen wir alle Großbuchstaben in Kleinbuchstaben umwandeln können Daher könnten Sie Ihren Code wie folgt ändern:
# Objektorientiertes Programmieren from cs50 import get_string # Aufforderung an den Benutzer zuzustimmen s = get_string("Sind Sie einverstanden?").lower() # Prüfen, ob zugestimmt wurde if s.lower() in ["y", "yes"]: print("Einverstanden.") elif s.lower() in ["n", "nein"]: print("Nicht einverstanden.")Man beachte, wie dadurch anstelle des Werts von
sdas Ergebnis vons.lower(), einer eingebauten Methode vonstr, verwendet wird.
Wichtige String-Methoden
| Methode | Beschreibung | Beispiel |
|---|---|---|
upper() | Alles in Großbuchstaben | "hallo".upper() → "HALLO" |
lower() | Alles in Kleinbuchstaben | "HALLO".lower() → "hallo" |
strip() | Leerzeichen vorn/hinten entfernen | " hi ".strip() → "hi" |
split() | String in Liste aufteilen | "a,b,c".split(",") → ["a","b","c"] |
find() | Position eines Teilstrings | "hallo".find("ll") → 2 |
replace() | Ersetzen | "hallo".replace("l","x") → "haxxo" |
startswith() | Beginnt mit…? | "hallo".startswith("ha") → True |
isdigit() | Nur Ziffern? | "123".isdigit() → True |
- Mehr über String-Methoden erfahren Sie in der Python-Dokumentation
Alles ist ein Objekt
- In Python ist alles ein Objekt – auch Dinge, bei denen man es nicht erwarten würde:
text = "Hello" print(type(text)) # <class 'str'> print(text.upper()) # Methodenaufruf auf String-Objekt numbers = [1, 2, 3] print(type(numbers)) # <class 'list'> numbers.append(4) # Methodenaufruf auf Listen-Objekt # Sogar Funktionen sind Objekte! def greet(): print("Hi") print(type(greet)) # <class 'function'> x = greet # x zeigt auf das Funktions-Objekt x() # Ruft greet() auf
Eigene Klassen definieren
Eine Klasse ist ein Bauplan für Objekte. Sie definiert Attribute (Daten) und Methoden (Verhalten):
class Rectangle: def __init__(self, width, height): # Initialisierung der Attribute self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) # Objekte (Instanzen) erstellen small_rect = Rectangle(3, 4) big_rect = Rectangle(10, 20) print(small_rect.area()) # 12 print(big_rect.perimeter()) # 60Wichtige Konzepte:
classdefiniert eine neue Klasse (Namenskonvention: PascalCase)__init__ist der Initialisierer – wird automatisch beim Erstellen aufgerufenselfist eine Referenz auf das aktuelle Objekt (erster Parameter jeder Methode)- Jedes Objekt hat seinen eigenen Zustand (hier: verschiedene Breiten/Höhen)
self verstehen
- Wenn wir eine Methode aufrufen, übergibt Python automatisch das Objekt als ersten Parameter:
class Student: def __init__(self, name): self.name = name self.courses = [] def enroll(self, course): self.courses.append(course) alice = Student("Alice") alice.enroll("Python") # Python macht intern daraus: # Student.enroll(alice, "Python")
Instanz- vs. Klassenattribute
- Instanzattribute gehören zu einem einzelnen Objekt, Klassenattribute werden von allen geteilt:
class Student: # Klassenattribut - von allen Instanzen geteilt school_name = "Python Academy" def __init__(self, name): # Instanzattribute - pro Objekt unterschiedlich self.name = name alice = Student("Alice") bob = Student("Bob") # Klassenattribut über die Klasse ändern Student.school_name = "Code School" print(alice.school_name) # "Code School" print(bob.school_name) # "Code School"
Kapselung: Private Attribute
- Per Konvention markiert ein Unterstrich
_ein Attribut als “privat” (nur intern verwenden). - Wichtig: Anders als in Java (wo
privatevom Compiler erzwungen wird) ist der Unterstrich in Python nur eine Konvention und ein visueller Hinweis. Python wirft keinen Fehler, wenn Sie von außen auf_balancezugreifen – es funktioniert einfach! Die Verantwortung liegt beim Programmierer.class BankAccount: def __init__(self, initial_balance): self._balance = initial_balance # Privat! def deposit(self, amount): if amount > 0: self._balance += amount def get_balance(self): return self._balance konto = BankAccount(1000) konto.deposit(500) print(konto.get_balance()) # 1500 print(konto._balance) # 1500 – funktioniert! Aber schlechter Stil. konto._balance = -100 # Auch das geht – Python vertraut dem Programmierer
Properties: Elegante Zugriffskontrolle
- Properties ermöglichen Validierung bei scheinbar direktem Attributzugriff:
class BankAccount: def __init__(self, initial_balance): self._balance = 0 self.balance = initial_balance # Nutzt Property! @property def balance(self): """Getter: Wird bei Lesezugriff aufgerufen""" return self._balance @balance.setter def balance(self, value): """Setter: Wird bei Schreibzugriff aufgerufen""" if value < 0: print("Fehler: Negativer Kontostand!") return self._balance = value konto = BankAccount(1000) print(konto.balance) # 1000 - nutzt Getter konto.balance += 500 # nutzt Setter mit Validierung konto.balance = -100 # "Fehler: Negativer Kontostand!"
Spezielle Methoden (Dunder Methods)
- Wenn Sie ein eigenes Objekt mit
print()ausgeben, erhalten Sie eine wenig hilfreiche Ausgabe wie<__main__.GradeBook object at 0x7f...>. Aber bei einer Liste wie[1, 2, 3]wird diese schön formatiert ausgegeben. Warum? Listen haben die Methode__str__implementiert, die Python aufruft, wenn ein Objekt als String dargestellt werden soll. - Dunder Methods (double underscore) ermöglichen es, dass Objekte mit Standard-Operationen funktionieren:
| Methode | Wird aufgerufen bei | Beispiel |
|---|---|---|
__str__ | print(obj) | String-Darstellung |
__len__ | len(obj) | Länge des Objekts |
__eq__ | obj1 == obj2 | Gleichheitsvergleich |
__getitem__ | obj[i] | Index-Zugriff |
Beispiel:
class GradeBook: def __init__(self): self._grades = [] def add(self, grade): self._grades.append(grade) def __str__(self): return f"Notenbuch mit {len(self._grades)} Noten" def __len__(self): return len(self._grades) def __getitem__(self, index): return self._grades[index] def __eq__(self, other): if not isinstance(other, GradeBook): return False return self._grades == other._grades buch = GradeBook() buch.add(85) buch.add(92) print(buch) # "Notenbuch mit 2 Noten" (nutzt __str__) print(len(buch)) # 2 (nutzt __len__) print(buch[0]) # 85 (nutzt __getitem__)Mehr zu Dunder Methods erfahren Sie in der Python-Dokumentation.
Schauen Sie sich das Short-Video zu OOP an, das Klassen und Objekte ausführlicher erklärt.
Schleifen
while-Schleifen in Python funktionieren auf den ersten Blick recht ähnlich wie Schleifen in C. Sie erinnern sich vielleicht an den folgenden Code in C:
// Demonstriert die while-Schleife #include <stdio.h> int main(void) { int i = 0; while (i < 3) { printf("miau\n"); i++; } }In Python sieht dieser Code wie folgt aus:
# Demonstriert die while-Schleife i = 0 while i < 3: print("miau") i += 1Die for-Schleife in Python ist anders als in C. In Python iteriert
fordirekt über eine Sammlung von Werten:# for iteriert über eine Liste for i in [1, 2, 3]: print("miau")Hier durchläuft
inacheinander die Werte 1, 2 und 3. Das ist eleganter als die C-Variante mit Zählervariable.Wenn wir nur eine bestimmte Anzahl von Durchläufen brauchen, verwenden wir
range():# range(3) erzeugt die Sequenz 0, 1, 2 for i in range(3): print("miau")Beachten Sie, dass
igar nicht benötigt wird. Python erhöht aber natürlich trotzdem in jedem Durchlauf (unnötigerweise) den Wert voni. Wenn man ausdrücken will, dass man den Wert gar nicht benötigt, kann man einen Unterstrich_statt desischreiben. Das macht den Code besser lesbar, weil es nicht zu Missverständnissen kommen kann (andernfalls könnte man sich irgendwann darüber wundern, dassinirgends verwendet wird)In ähnlicher Weise könnte man den obigen Code wie folgt ausdrücken:
# Abstraktion mit Parametrisierung # Miau eine bestimmte Anzahl von Malen def miau(n): for _ in range(n): print("miau") miau(3)Beachten Sie, dass nun eine Funktion verwendet wird, um das Miauen auszulagern.
Um unser Verständnis von Schleifen und Iteration in Python zu vertiefen, erstellen wir eine neue Datei mit dem Namen
uppercase.py, die sich an C-Code anlehnt, den wir früher schon einmal geschrieben haben:# Zeichenkette mit Großbuchstaben, ein Zeichen nach dem anderen before = input("Vorher: ") print("Nachher: ", end="") for c in before: print(c.upper(), end="") print()Beachten Sie, wie elegant wir mit
for c in beforeüber jeden Buchstaben im String iterieren können! In C müssten wir schreiben:for (int i = 0; i < strlen(before); i++)und dann mitbefore[i]auf jeden Buchstaben zugreifen. In Python geht das auch (mitfor i in range(len(before)): print(before[i].upper())), aber die direkte Iteration über den String ist viel lesbarer.Beachten Sie auch, wie der benannte Parameter
end=verwendet wird, um einen Parameter an die Funktionprintzu übergeben. Wir brauchen hier einen benannten Parameter, weil print(…) für mehr Komfort so konstruiert wurde, dass es alle Positions-basierten Parameter mit Leerzeichen getrennt ausgibt. Dadurch gibt es keinen natürlichen Ort, wo wir einen Parameter zur Veränderung des Verhaltens platzieren können – und eine separate Funktionprint_without_newlinewäre nicht besonders elegant. Mit benannten Parametern können wir einer Methode unabhängig von der Position Parameter übergeben, auf die in der Methode zugegriffen werden kann. Beiprintkönnen wir dadurch das Standardverhalten verändern. Es gibt dann keinen Zeilenumbruch aus.Bei der Lektüre der Dokumentation stellen wir fest, dass die
upper()-Methode vonstrimmer die gesamte Zeichenkette verarbeitet und wir daher weniger Code benötigen:# Zeichenkette auf einmal großschreiben before = input("Vorher: ") after = before.upper() print(f"Nachher: {after}")Beachten Sie, dass “upper” auf die gesamte Zeichenkette angewendet wird.
Oder noch kürzer
# Zeichenkette auf einmal großschreiben before = input("Vorher: ") print(f"Nachher: {before.upper()}")
Vorschau: Was macht etwas “iterierbar”?
- Sie haben gesehen, dass hinter
inbei einer for-Schleife sowohl eine Liste ([1, 2, 3]) als auch ein String ("Hallo") stehen kann. Aber was passiert, wenn Siefor i in 42schreiben? Python wirft einen Fehler:TypeError: 'int' object is not iterable. - Es scheint also so zu sein, dass das, was hinter
insteht, etwas “Besonderes” sein muss. Dieses “Besondere” ist nicht fest in der Sprache kodiert, sondern es ist eine bestimmte Methode (__iter__), die implementiert sein muss, damit etwas “iterierbar” ist. - Mehr dazu erfahren Sie in der nächsten Vorlesung!
Abstraktion mit Funktionen
Wie wir heute schon angedeutet haben, können wir unseren Code verbessern, indem wir Funktionen verwenden und die Implementierungsdetails in Funktionen abstrahieren. Ändern Sie Ihren zuvor erstellten
miau.pyCode wie folgt:# Abstraktion def main(): for i in range(3): miau() # Einmal miauen def miau(): print("miau") main()Beachten Sie, dass die Funktion
miaudie Anweisungprintabstrahiert. Beachten Sie auch, dass die Funktionmainam Anfang der Datei definiert ist und am Ende der Datei aufgerufen wird. Dies ist eine gängiges Programmiermuster in Python. Funktionen können erst aufgerufen werden nachdem sie definiert wurden. Weil es das Konzept der Funktionsprototypen nicht gibt, rufen wir einfach main() ganz am Ende der Datei auf – zu diesem Zeitpunkt hat der Python-Interpreter die ganze Datei eingelesen und kennt alle Funktionen, die main() kann.Wie aus C bekannt können wir Variablen zwischen Funktionen übergeben:
# Abstraktion mit Parametrisierung def main(): miau(3) # Miau eine bestimmte Anzahl von Malen def miau(n): for i in range(n): print("miau") main()Beachten Sie, dass
miaujetzt eine Variablenbenötigt. In der Funktionmainkönnen Siemiauaufrufen und ihm einen Wert wie3übergeben. Dann verwendet “miau” den Wert von “n” in der “for”-Schleife.Wenn Sie den obigen Code lesen, werden Sie feststellen, dass Sie als C-Programmierer in der Lage sind, den obigen Code ganz leicht zu verstehen. Zwar sind einige Konventionen anders, aber die Bausteine, die Sie zuvor gelernt haben, sind für Sie in dieser neuen Programmiersprache nun schneller nachvollziehbar.
Standardwerte für Parameter
- Funktionen können Standardwerte für Parameter haben:
def begrüsse(name="Welt"): print(f"Hallo {name}") begrüsse() # "Hallo Welt" begrüsse("Alice") # "Hallo Alice"
Mehrere Rückgabewerte
- Python-Funktionen können mehrere Werte zurückgeben:
def get_kreis_info(radius): umfang = 2 * 3.14 * radius flaeche = 3.14 * radius * radius return umfang, flaeche # Aufruf mit Entpacken u, f = get_kreis_info(5) print(f"Umfang: {u}, Fläche: {f}")
Docstrings
Funktionen sollten mit einem Docstring dokumentiert werden:
def quadrat(x): """Berechnet das Quadrat einer Zahl. Args: x: Die zu quadrierende Zahl Returns: Das Quadrat von x """ return x * xDer Docstring steht direkt nach der Funktionsdefinition in dreifachen Anführungszeichen.
Keine Trunkierung bei Division und Fließkomma-Ungenauigkeit
Erinnern Sie sich, dass wir in C die Erfahrung gemacht haben, dass die Division einer ganzen Zahl durch eine andere zu einem ungenauen, trunkierten Ergebnis führen kann.
Schauen wir uns an, wie Python eine solche Division handhabt, indem wir den Code für
calculator.pyändern:# Division mit ganzen Zahlen, Nachweis der fehlenden Trunkierung x = int(input("x: ")) y = int(input("y: ")) z = x / y print(z)Beachten Sie, dass automatisch ein Fließkomma-Wert ausgegeben wird – in C wäre das Ergebnis der Division hingegen auf die nächste Ganzzahl abgerundet (trunkiert) worden. Wenn Sie sich mehr Ziffern nach
.333333ausgeben lassen würden, würden Sie sehen, dass wir auch in Python mit Gleitkomma-Ungenauigkeit leben müssen.Wir können diese Ungenauigkeit aufdecken, indem wir unsere Codes leicht abändern:
# Fließkomma-Ungenauigkeit # Benutzer zur Eingabe von x auffordern x = int(input("x: ")) # Benutzer zur Eingabe von y auffordern y = int(input("y: ")) # x durch y dividieren z = x / y print(f"{z:.50f}")Hier sehen Sie ein Beispiel für eine weiterführende Syntax von Format-Strings, die so ähnlich funktioniert wie bei
printfin C. Wir geben die Zahl mit 50 Nachkommastellen aus. Dadurch sehen wir die Ungenauigkeit von Fließkommazahlen.
Exceptions
Sehen wir uns noch einmal folgenden Code an:
from cs50 import get_int def main(): # Benutzer zur Eingabe von x auffordern x = get_int("x: ") # Benutzer zur Eingabe von y auffordern y = get_int("y: ") # Addition durchführen print(x + y) main()Wenn Sie bei diesem Programm statt einer Zahl einen String eingeben (z.B.
cat), dann werden Sie erneut aufgefordert eine Zahl einzugeben. Die CS50-Bibliothek kümmert sich intern um die Fehlerbehandlung. Wie macht sie das?Das beobachtete Verhalten ist nicht das Standardverhalten von Python. Das sehen wir, wenn wir versuchen,
get_intnachzubauen.Ändern Sie
calculator.pywie folgt:# Implementiert get_int def get_int(prompt): return int(input(prompt)) def main(): # Benutzer zur Eingabe von x auffordern x = get_int("x: ") # Benutzer zur Eingabe von y auffordern y = get_int("y: ") # Addition durchführen print(x + y) main()Beachten Sie, dass die Eingabe von falschen Daten zu einem Fehler führt (
ValueError) und das Programm abgebrochen wird.In Python und anderen modernen Programmiersprachen gibt es ein leistungsfähiges Konstrukt zur Fehlerbehandlung, sogenannte Exceptions.
Die Idee ist folgende: Anstatt für alle möglichen Fehlerfälle explizit mit bedingten Anweisungen Prüfschritte einzubauen, „versuchen“ (engl. to try), wir den Code auszuführen und behandeln (man sagt: “fangen”, engl. to catch) dabei möglicherweise auftretende Ausnahmezustände. Dazu ändern wir unseren Code wie wie folgt:
# Implementiert get_int mit einer Schleife def get_int(prompt): while True: try: return int(input(prompt)) except ValueError: print("Not an integer") def main(): # Benutzer zur Eingabe von x auffordern x = get_int("x: ") # Benutzer zur Eingabe von y auffordern y = get_int("y: ") # Addition durchführen print(x + y) main()Beachten Sie, dass der obige Code wiederholt versucht, den richtigen Datentyp abzurufen, und bei Bedarf zusätzliche Eingabeaufforderungen liefert. So ähnlich arbeitet auch
get_intim cs50-Modul.
Testen mit pytest
pytest macht das systematische Testen einfach. Testfunktionen beginnen mit
test_:# test_calculator.py import pytest def test_addition(): assert 2 + 2 == 4 def test_division_by_zero(): with pytest.raises(ZeroDivisionError): result = 1 / 0Tests ausführen:
pytest test_calculator.py # Einzelne Datei pytest -v # Alle Tests, ausführliche AusgabeSchauen Sie sich das Short-Video zu Fehlerbehandlung an, das Exceptions und pytest ausführlicher erklärt.
Vertiefung: Weitere Exception-Konzepte (nicht in der Vorlesung behandelt)
Die folgenden Konzepte sind im Short-Video erklärt.
Eigene Exceptions auslösen (raise)
- Mit
raisekönnen Sie selbst Exceptions auslösen:def withdraw_money(balance, amount): if amount > balance: raise ValueError("Not enough money in account") return balance - amount try: new_balance = withdraw_money(100, 200) except ValueError as e: print(f"Error: {e}") # "Error: Not enough money in account"
Mehrere Exception-Typen behandeln
Verschiedene Fehler können unterschiedlich behandelt werden:
try: number = int(input("Zahl eingeben: ")) result = 100 / number except ValueError: print("Bitte eine gültige Zahl eingeben") except ZeroDivisionError: print("Division durch Null nicht erlaubt")Oder mehrere auf einmal behandeln:
try: result = risky_operation() except (ValueError, ZeroDivisionError) as error: print(f"Fehler: {error}")
try – except – else
- Der
else-Block wird nur ausgeführt, wenn kein Fehler auftritt:try: result = int(input("Zahl: ")) except ValueError: print("Ungültige Eingabe") else: print(f"Quadrat: {result ** 2}") # Nur wenn Konvertierung erfolgreich
Listen und Dictionaries (Vorschau)
Python bietet mächtige eingebaute Datenstrukturen: Listen (
list) und Wörterbücher (dict).Eine Liste ist eine geordnete Sammlung von Werten:
scores = [72, 73, 33] print(sum(scores) / len(scores)) # Durchschnitt: 59.33...Ein Dictionary speichert Schlüssel-Wert-Paare und ermöglicht schnellen Zugriff:
telefonbuch = { "Alice": "+49-123-456", "Bob": "+49-789-012" } print(telefonbuch["Alice"]) # +49-123-456In der nächsten Vorlesung werden wir Listen und Dictionaries ausführlich behandeln und sehen, wie man sie geschickt für effiziente Datenverarbeitung einsetzt.
Schauen Sie sich schon mal das Short-Video zu Collections an, um einen Vorgeschmack zu bekommen.
Zusammenfassend
In dieser Vorlesung haben Sie gelernt, wie die Bausteine der Programmierung aus früheren Vorlesungen in Python genutzt werden können. Außerdem haben Sie gelernt, wie mit Pythons Syntax einfacher Code geschrieben werden kann und wie Sie gängige Python-Funktion nutzen können.
Vermutlich haben Sie in dieser Vorlesung einige der Konzepte auf Anhieb verstanden. Das liegt nicht nur daran, dass die Syntax von Python leichter zugänglich ist als die Syntax von C. Es liegt auch daran, dass die Fähigkeiten und das Wissen aus den letzten Wochen Ihnen den Einstieg in eine neue Programmiersprache erleichtern. Sie verfügen nun über eine solide Basis, mit der Sie sich selbst neue Programmiersprachen aneignen können!
Vielleicht können Sie jetzt erahnen, wie Sie durch die Herangehensweise, die wir in diesem Kurs verfolgen, eine neue Art des Lernens entdecken – neugierig zu sein (Was passiert eigentlich, wenn ich hier … eingebe?) und nachzuhaken, um den Dingen auf den Grund zu gehen (Wieso ist das eigentlich so?). Diese Art des Lernens wird Ihnen hoffentlich beim Erlernen weiterer Programmiersprachen - und vielleicht auch beim Lernen in anderen Bereichen - von Nutzen sein.
Konkret haben wir besprochen…
- Python
- Variablen und PEP 8
- Typen
- Bedingte Anweisungen (inkl. Truthiness, ternärer Operator, is vs ==)
- Schleifen
- Objektorientiertes Programmieren (Klassen, init, self, Properties, Dunder Methods)
- Funktionen (Standardwerte, Multiple Returns, Docstrings)
- Trunkierung und Fließkomma-Ungenauigkeit
- Ausnahmen (try/except) und Testen mit pytest
- Listen und Dictionaries (Vorschau)
Die Fortsetzung folgt in der nächsten Vorlesung!