Notes 7
Sie können diese Seite ausdrucken oder mit einem PDF-Drucker in ein PDF umwandeln, um Ihre eigenen Notizen hinzuzufügen.
Willkommen!
- In der letzten Vorlesung haben Sie die Grundlagen von Python kennengelernt: Syntax, Typen, Kontrollfluss, Funktionen, objektorientierte Programmierung und Fehlerbehandlung.
- In dieser Vorlesung vertiefen wir diese Konzepte und lernen elegante Muster für die Datenverarbeitung kennen.
ÜBUNGSAUFGABE 1 - Schleifen (Lückentext)
Ziel: Wiederholung for-Schleifen und String-Methoden aus der letzten Vorlesung
Aufgabe: Füllen Sie die zwei Lücken aus, sodass das Programm jeden Buchstaben eines Strings in Großbuchstaben ausgibt.
text = "hallo"
for ___(1)___ in text:
print(___(2)___)Optionen für (1): i, c, text[i], range(text)
Optionen für (2): c.upper(), upper(c), c.toupper(), text.upper()
Lösung:
- (1):
c— Schleifenvariable, die jeden Buchstaben durchläuft - (2):
c.upper()— Methode auf dem String-Objekt aufrufen
Erklärung:
- In Python iteriert
for c in textdirekt über jeden Buchstaben upper()ist eine Methode vonstr, Aufruf mit Punkt-Notation
Häufige Fehler:
upper(c)stattc.upper()→ In Python sind Methoden an Objekte gebundentext.upper()→ Gibt den ganzen String aus, nicht einzelne Buchstaben
ÜBUNGSAUFGABE 2 - Truthiness (✓/✗)
Ziel: Wiederholung bedingte Anweisungen und “falsy”/“truthy” Werte
Aufgabe: In Python können viele Werte in if-Bedingungen verwendet werden. Welche Bedingungen sind True?
if 0: # ?
if "": # ?
if "hallo": # ?
if []: # ?
if [1, 2, 3]: # ?
if 42: # ?Lösung:
if 0:✗ — Null ist falsyif "":✗ — Leerer String ist falsyif "hallo":✓ — Nicht-leerer String ist truthyif []:✗ — Leere Liste ist falsyif [1, 2, 3]:✓ — Nicht-leere Liste ist truthyif 42:✓ — Jede Zahl außer 0 ist truthy
Merke: Leere Dinge (0, “”, []) sind False, gefüllte Dinge sind True!
Bibliotheken von Drittanbietern
Einer der Vorteile von Python ist seine riesige Benutzerbasis und die ebenso große Anzahl von Bibliotheken von Drittanbietern.
Mit dem Befehl
pip installkönnen Sie zusätzliche Bibliotheken installieren:pip install qrcodeDann können Sie mit wenigen Zeilen einen QR-Code generieren:
import qrcode img = qrcode.make("https://psi.2i.vc/inf-einf-b/") img.save("kurs.png")Sie können mehr über verfügbare Pakete auf PyPI erfahren.
Codequalität und Typsicherheit
Defensives Programmieren
Prüfen Sie Eingaben frühzeitig und geben Sie aussagekräftige Fehlermeldungen:
def set_age(age): if not isinstance(age, int): raise TypeError("Alter muss eine Ganzzahl sein") if age < 0 or age > 150: raise ValueError(f"Ungültiges Alter: {age}") return ageisinstance(age, int)prüft, obagevom Typintist – nützlich für Typprüfungen zur Laufzeit.Mit
raiselösen wir Exceptions aus, um Fehlerzustände zu signalisieren.
Type Hints
Type Hints dokumentieren erwartete Typen (werden aber zur Laufzeit nicht geprüft):
def calculate_average(numbers: list[float]) -> float: if not numbers: raise ValueError("Liste darf nicht leer sein") return sum(numbers) / len(numbers) # IDE zeigt Warnung, aber Code läuft: result = calculate_average(["1", "2"]) # TypeError bei sum()!Type Hints sind nützlich für Dokumentation und IDE-Unterstützung, ersetzen aber keine Laufzeitprüfungen.
Kommandozeilen-Argumente
Wie bei C können Sie auch in Python Kommandozeilen-Argumente verwenden. Betrachten Sie den folgenden Code:
# Druckt ein Kommandozeilenargument from sys import argv if len(argv) == 2: print(f"hallo, {argv[1]}") else: print("hallo, Welt")Beachten Sie, dass
argv[1]unter Verwendung einer formatierten Zeichenkette gedruckt wird, erkennbar an demfin derprint-Anweisung.Sie können alle Argumente in
argvwie folgt ausgeben:# Kommandozeilenargumente ausgeben, Indizierung in argv from sys import argv for i in range(len(argv)): print(argv[i])Beachten Sie, dass
argv[0]immer der Name des Skripts selbst ist (z.B.greet.py), nicht der Python-Interpreter.Sie können Teile von Listen wegschneiden (engl. to slice). Betrachten Sie den folgenden Code:
# Kommandozeilenargumente drucken from sys import argv for arg in argv[1:]: print(arg)Beachten Sie, dass die Ausführung dieses Codes dazu führt, dass der Name der Datei, die Sie ausführen, weggeschnitten wird. Es werden alle Argumente ab dem ersten (nullindiziert, also eigtl. ab dem zweiten) ausgegeben.
Sie können mehr über die
sys-Bibliothek in der Python-Dokumentation erfahren.
Exit-Code
Die
sys-Bibliothek hat auch eingebaute Methoden. Wir könnensys.exit(i)verwenden, um das Programm mit einem bestimmten Exit-Code zu beenden:# Beendet mit explizitem Wert, importiert sys import sys if len(sys.argv) != 2: print("Fehlendes Kommandozeilenargument") sys.exit(1) print(f"hallo, {sys.argv[1]}") sys.exit(0)Beachten Sie, dass die Punkt-Notation verwendet wird, um die eingebauten Funktionen von “sys” zu nutzen, da wir das ganze sys-Modul importieren.
OOP-Patterns für größere Programme
In der letzten Vorlesung haben wir Klassen, __init__, self, Properties und Dunder Methods kennengelernt. Nun betrachten wir fortgeschrittene Muster.
Vererbung
In der letzten Vorlesung haben wir die Klasse
Rectanglemitarea()undperimeter()erstellt.Problem: Was, wenn wir auch
Circleoder andere Formen brauchen? Jede Form hättearea()undperimeter()– das ist Code-Duplikation!Lösung: Wir definieren eine gemeinsame Basisklasse
Shape, von der andere Klassen erben.Vererbung ermöglicht es, Klassen zu erstellen, die Eigenschaften einer anderen Klasse übernehmen und erweitern.
Die Syntax lautet
class Kindklasse(Elternklasse):– die Kindklasse erbt alle Methoden der Elternklasse.class Shape: """Basisklasse für geometrische Formen.""" def area(self): pass # Platzhalter – wird von Kindklassen überschrieben def perimeter(self): pass # Platzhalter class Circle(Shape): # Circle erbt von Shape def __init__(self, radius): self.radius = radius def area(self): # Methode überschreiben return 3.14159 * self.radius ** 2 def perimeter(self): return 2 * 3.14159 * self.radius class Rectangle(Shape): # Rectangle erbt auch von Shape def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height)passist eine leere Anweisung, die nichts tut – nützlich als Platzhalter für noch nicht implementierten Code.Vorteil: Wir können alle Formen gleich behandeln, weil sie dieselbe Schnittstelle haben:
# Alle Subklassen gleich behandeln shapes = [Circle(5), Rectangle(3, 4)] for shape in shapes: print(f"Fläche: {shape.area()}")
super() ruft die Methode der Elternklasse auf – nützlich, wenn die Kindklasse die Initialisierung der Elternklasse nutzen will:
class Square(Rectangle): """Ein Quadrat ist ein spezielles Rechteck.""" def __init__(self, side_length): # Ruft Rectangle.__init__(self, width, height) auf super().__init__(side_length, side_length) square = Square(5) print(square.area()) # 25 - nutzt Rectangle.area() print(square.perimeter()) # 20 - nutzt Rectangle.perimeter()super().__init__(...)sorgt dafür, dass die Elternklasse ihre Attribute (width,height) korrekt setzt.Square muss
area()undperimeter()nicht neu definieren – es erbt sie von Rectangle.Wann Vererbung nutzen? Bei “ist-ein”-Beziehungen: Ein Square ist ein (spezielles) Rectangle. Ein Circle ist eine Shape.
Duck Typing
In Python zählt nicht der Typ eines Objekts, sondern sein Verhalten:
“Wenn es wie eine Ente watschelt und wie eine Ente quakt, ist es eine Ente.”
Solange ein Objekt die erwarteten Methoden hat, funktioniert es – egal welche Klasse:
class Dog: def make_sound(self): return "Wuff!" class Cat: def make_sound(self): return "Miau!" # Python interessiert sich nicht für Typen, nur für Verhalten def animal_chorus(animals): for animal in animals: print(animal.make_sound()) animal_chorus([Dog(), Cat()]) # Funktioniert!Mit
hasattr()kann man prüfen, ob ein Objekt eine Methode unterstützt:if hasattr(obj, 'take_damage'): obj.take_damage(10)
Separation of Concerns
- Gutes Design trennt verschiedene Zuständigkeiten. Jede Klasse sollte eine klare, einzelne Aufgabe haben.
- Beispiel: Die Spiellogik (Schaden berechnen) sollte von der Ausgabe (Nachrichten anzeigen) getrennt sein.
- Dadurch wird Code flexibler, leichter testbar und besser wartbar. Details zu diesem Designprinzip behandeln wir in einer späteren Session.
Collections vertieft
In der letzten Vorlesung haben wir Listen und Dictionaries kurz angeteasert. Nun schauen wir uns diese wichtigen Datenstrukturen genauer an.
Listen im Detail
listist eine sehr häufig genutzte Datenstruktur in Python.Für die Verarbeitung von Listen gibt es in Python einige nützliche Funktionen – und Listen haben zusätzlich viele nützliche Methoden.
Betrachten Sie zum Beispiel den folgenden Code:
# Durchschnitt von drei Zahlen in einer Liste # Punkte scores = [72, 73, 33] # Durchschnitt ausgeben average = sum(scores) / len(scores) print(f"Durchschnitt: {average}")Beachten Sie, dass Sie die eingebaute Funktion
sumverwenden können, um den Durchschnitt zu berechnen.Man könnte sich fragen, wieso man
sum(scores)schreibt und nichtscores.sum()– gleichermaßen bei len(…). Das liegt unter anderem daran, dass Listen nicht die einzigen Objekte sind, für die man eine Summe berechnen oder die Anzahl ermitteln können will. Diese Operationen könnten auch bei Tupeln oder Mengen nützlich sein. Tatsächlich sind sie bei allen Objekten sinnvoll, die man zählen kann bzw. die mehrere aufsummierbare Werte haben. Anstatt solche Funktionen in allen Klassen, in denen es Sinn macht, neu zu definieren, gibt es sie daher in Python global. Klassen, die sich summieren oder zählen lassen wollen, müssen diese Eigenschaft dann durch vordefinierte Methoden (sog. Dunder-Methods von „Double-Underscore") anbieten.
Sie können die folgende Syntax verwenden, um Werte vom Benutzer zu erhalten:
# Durchschnitt von drei Zahlen mit Hilfe einer Liste und einer Schleife from cs50 import get_int # Spielstände abrufen scores = [] for i in range(3): score = get_int("Score: ") scores.append(score) # Alternative: scores + [score] # Durchschnitt ausgeben average = sum(scores) / len(scores) print(f"Durchschnitt: {average}")Beachten Sie, dass dieser Code die eingebaute Methode
appendfür Listen verwendet.Sie können mehr über Listen in der Python-Dokumentation erfahren.
Sie können auch mehr über
lenin der Python-Dokumentation erfahren.
Suche in Collections
Wir haben uns in C angesehen, wie man in Datenstrukturen nach einem bestimmten Wert sucht.
In Python können wir eine lineare Suche sehr elegant mit dem
in-Operator durchführen:# Implementiert die lineare Suche nach Namen mit "in". # Eine Liste von Namen names = ["Carter", "David", "John"] # Nach dem Namen fragen name = input("Name: ") # Suche nach Name if name in names: print("Gefunden") else: print("Nicht gefunden")Beachten Sie, wie “in” zur Umsetzung der linearen Suche verwendet wird.
Dieser Code ist zwar elegant, aber nicht effizient.
Sie erinnern sich vielleicht noch daran, dass ein Wörterbuch (Pythons Datentyp
dict, der mit geschweiften Klammern definiert wird) eine Sammlung von Schlüssel- und _Wert-_Paaren ist und dass Wörterbücher mit Hash-Tabellen sehr effizient implementiert werden können (konstante Laufzeit für die Suche bei Wahl einer geeigneten Hash-Funktion).Man könnte das Telefonbuch auch als Liste von Dictionaries implementieren (ähnlich wie
structin C):# Telefonbuch als Liste von Dictionaries (wie structs in C) people = [ {"name": "Fuhrmann", "num": "+1-617-495-1000"}, {"name": "David", "num": "+1-617-495-1000"}, {"name": "John", "num": "+1-949-468-2750"}, ] # Suche – lineare Suche durch die Liste nötig! name = input("Name: ") for person in people: if person["name"] == name: print(f"Nummer: {person['num']}") breakDas funktioniert, aber wir müssen die ganze Liste durchsuchen (lineare Laufzeit).
Besser: Wir nutzen ein echtes Dictionary, bei dem der Name direkt der Schlüssel ist – dann ist die Suche in konstanter Zeit möglich!
Für unser einfaches Telefonbuch benötigen wir streng genommen weder
namenochnumals separate Felder! Wir können diesen Code wie folgt vereinfachen:# Implementiert ein Telefonbuch mit dict from cs50 import get_string people = { "Fuhrmann": "+1-617-495-1000", "David": "+1-617-495-1000", "John": "+1-949-468-2750", } # Suche nach Name name = get_string("Name: ") if name in people: print(f"Nummer: {people[name]}") else: print("Nicht gefunden")Beachten Sie, dass people nun ein dict ist und mit geschweiften Klammern definiert wird. Die Anweisung
if name in peopleprüft, obnameals Key im Wörterbuchpeopleenthalten ist. Beachten Sie auch, dass wir in der Anweisungprintmit dem Wert vonnameim People-Wörterbuch indexieren können. Sehr nützlich!Python gibt sein Bestes, um bei der Suche eine konstante Zeit zu erreichen, indem es seine eingebauten Suchfunktionen verwendet.
Sie können mehr über Wörterbücher in der Python-Dokumentation erfahren.
Variablen als Namensschilder
In C sind Variablen wie Behälter, die einen Wert speichern. In Python funktioniert das anders: Variablen sind wie Namensschilder, die an Objekten hängen.
# Ein Objekt, ein Name x = 42 print(x) # 42 # Ein Objekt, zwei Namen y = x # y zeigt auf dasselbe 42 print(y) # 42 # Namensschild umhängen x = 23 # x zeigt jetzt auf 23 print(y) # Immer noch 42!Mehrere Namensschilder können auf dasselbe Objekt zeigen:
noten = [95, 92, 98] kopie = noten # Kein neuer Container! # Beide Namen zeigen auf dieselbe Liste kopie.append(90) # Wirkt sich auf 'noten' aus print(noten) # [95, 92, 98, 90]Um eine echte Kopie zu erstellen:
echte_kopie = list(noten) # Neues Listenobjekt # oder: echte_kopie = noten.copy()
Mutable vs. Immutable
In Python gibt es zwei Arten von Objekten:
- Immutable (unveränderlich): Zahlen (int, float), Strings, Tupel
- Mutable (veränderlich): Listen, Dictionaries, Sets
Bei immutablen Objekten erzeugt jede “Änderung” ein neues Objekt:
x = 42 x += 1 # Erzeugt neues Objekt 43 text = "Python" text[0] = "J" # Fehler! Strings sind immutable text = "J" + text[1:] # Neuen String erstellen: "Jython"Bei mutablen Objekten wird das Objekt selbst verändert:
lst = [1, 2] lst.append(3) # Gleiches Objekt wird verändertWichtig: Bei Funktionsparametern verhält sich das unterschiedlich:
# Immutable: Funktioniert wie "Pass by Value" def increment(x): x = x + 1 # Neues Namensschild! print(f"In Funktion: {x}") # 43 zahl = 42 increment(zahl) print(f"Nach Aufruf: {zahl}") # Immer noch 42! # Mutable: Funktioniert wie "Pass by Reference" def add_grade(grades): grades.append(100) # Ändert das Original! print(f"In Funktion: {grades}") noten = [95, 92, 98] add_grade(noten) print(f"Nach Aufruf: {noten}") # [95, 92, 98, 100]Achtung: Methoden auf immutablen Objekten geben neue Objekte zurück:
text = "hallo" gross = text.upper() # Gibt geänderte Kopie zurück print(text) # Immer noch "hallo" print(gross) # "HALLO"
Tupel
Tupel sind wie Listen, aber immutable. Sie werden mit runden Klammern erstellt:
punkt = (3, 4) # Tupel erstellen punkt = 3, 4 # Klammern sind optional x = punkt[0] # Zugriff wie bei Listen: 3 # punkt[0] = 5 # Fehler! Tupel sind immutableDer Hauptnutzen von Tupeln zeigt sich bei mehreren Rückgabewerten:
def get_bounds(numbers): return min(numbers), max(numbers) # Tupel! # Automatisches Entpacken minimum, maximum = get_bounds([1, 2, 3, 4, 5]) print(f"Min: {minimum}, Max: {maximum}")Der berühmte Swap mit Tupeln:
x = 5 y = 10 x, y = y, x # Tupel-Magic!Teilweises Entpacken mit
_für ignorierte Werte:def get_statistik(zahlen): return sum(zahlen), sum(zahlen)/len(zahlen), min(zahlen) summe, mittel, _ = get_statistik([1, 2, 3, 4, 5]) # _ ignoriert den min-WertWichtig: Tupel können als Dictionary-Schlüssel verwendet werden (weil sie immutable sind), Listen nicht!
d = {(1, 2): "Tupel als Schlüssel"} # OK # d = {[1, 2]: "Liste als Schlüssel"} # Fehler!
Sets (Mengen)
- Sets (
set) speichern eindeutige Werte ohne Duplikate und ermöglichen Mengenoperationen wie Vereinigung (|), Schnittmenge (&) und Differenz (-). - Syntax:
{1, 2, 3}oderset([1, 2, 2, 3])→{1, 2, 3} - Praktisch zum Entfernen von Duplikaten:
eindeutig = set(liste_mit_duplikaten) - Die Prüfung auf Enthaltensein ist bei Sets schneller als bei Listen (dank Hash-Tabelle).
Nützliche Dictionary-Methoden
Sicherer Zugriff mit
.get(): Vermeidet KeyError, wenn Schlüssel nicht existiert:noten = {"Alice": 95, "Bob": 87} # Direkter Zugriff kann fehlschlagen: # print(noten["Eve"]) # KeyError! # Sicherer mit .get() und Standardwert: note = noten.get("Eve", 0) # 0, wenn "Eve" nicht existiert print(note) # 0Über Dictionaries iterieren:
students = { "Alice": {"Note": 95, "Fach": "Informatik"}, "Bob": {"Note": 87, "Fach": "Physik"} } # Nur über Schlüssel (Standard) for name in students: print(name) # Über Schlüssel-Wert-Paare mit .items() for name, info in students.items(): print(f"{name} studiert {info['Fach']}") # Nur über Werte mit .values() for info in students.values(): print(info["Note"])Mehrere Einträge hinzufügen mit
.update():noten = {"Alice": 95, "Bob": 87} noten.update({"Charlie": 91, "David": 88}) # noten ist jetzt: {"Alice": 95, "Bob": 87, "Charlie": 91, "David": 88}
Geschachtelte Collections
- Collections können andere Collections enthalten. Das ermöglicht komplexe Datenstrukturen:
Mehrdimensionale Listen
- 2D-Listen funktionieren wie 2D-Arrays in C:
# Tic-Tac-Toe Brett brett = [ [" ", "X", "O"], ["X", "O", " "], ["O", " ", "X"] ] print(brett[0]) # Erste Zeile: [" ", "X", "O"] print(brett[1][1]) # Mitte: "O"
Strukturierte Daten mit Dictionaries
Dictionaries lassen sich elegant verschachteln:
kurse = { "Python": { "teilnehmer": [ {"name": "Alice", "note": 95}, {"name": "Bob", "note": 87} ], "raum": "A101", "zeiten": ["Mo 14:00", "Mi 16:00"] }, "Java": { "teilnehmer": [ {"name": "Charlie", "note": 92} ], "raum": "B205" } } # Gezielter Zugriff print(kurse["Python"]["teilnehmer"][0]["note"]) # 95 # Iteration über verschachtelte Strukturen for kurs, info in kurse.items(): print(f"\nKurs: {kurs}") print(f"Raum: {info['raum']}") for student in info['teilnehmer']: print(f"- {student['name']}: {student['note']}")Tipp: Viele Freiheiten ermöglichen hohe Komplexität. Das begünstigt Fehler – daher ist Zurückhaltung und gute Dokumentation wichtig!
JSON-Daten verarbeiten
JSON (JavaScript Object Notation) ist ein weit verbreitetes Format für den Datenaustausch zwischen Systemen.
JSON sieht Python-Dictionaries sehr ähnlich – und Python kann JSON direkt in Dictionaries umwandeln.
Mit dem
json-Modul können Sie JSON-Daten verarbeiten:import json # JSON-String in Dictionary umwandeln json_text = '{"name": "Alice", "alter": 25, "kurse": ["Python", "Java"]}' daten = json.loads(json_text) # loads = "load string" print(daten["name"]) # Alice print(daten["kurse"][0]) # PythonJSON-Dateien können Sie mit
json.load()einlesen:# JSON aus Datei lesen with open("config.json") as f: config = json.load(f) # load (ohne 's') für DateienDas
with-Statement ist ein sog. Context Manager: Es sorgt automatisch dafür, dass die Datei nach dem Block geschlossen wird – auch wenn ein Fehler auftritt.Praktische Anwendungen: API-Antworten verarbeiten, Konfigurationsdateien lesen, Daten speichern und laden.
Elegante Datenverarbeitung
Praktische Iterationshelfer
enumerate()liefert Index und Element zusammen:spieler = ["Anna", "Bob", "Charlie"] for i, name in enumerate(spieler): print(f"Spieler {i+1}: {name}")zip()iteriert parallel über mehrere Listen:namen = ["Anna", "Bob", "Charlie"] punkte = [95, 82, 88] for name, punkt in zip(namen, punkte): print(f"{name}: {punkt}")
Seiteneffekte und reine Funktionen
Ein Seiteneffekt ist eine Änderung, die über den Rückgabewert hinausgeht – z.B. eine globale Variable ändern oder eine Liste direkt modifizieren.
Reine Funktionen (pure functions) haben keine Seiteneffekte: Gleicher Input ergibt immer gleichen Output, ohne “versteckte” Änderungen.
# Funktion MIT Seiteneffekt highscore = 0 def update_highscore(punkte): global highscore # Erlaubt Zugriff auf Variable außerhalb der Funktion if punkte > highscore: highscore = punkte # Ändert globale Variable! return highscore # Reine Funktion OHNE Seiteneffekt def get_new_highscore(alter_highscore, neue_punkte): return max(alter_highscore, neue_punkte) # Nur Input → OutputDas Keyword
globalerlaubt einer Funktion, auf eine Variable außerhalb ihres lokalen Scopes zuzugreifen und sie zu ändern. Ohneglobalwürde Python eine neue lokale Variable anlegen.Warum sind reine Funktionen besser?
- Leichter zu testen (keine versteckten Abhängigkeiten)
- Vorhersagbarer (gleicher Input = gleicher Output)
- Einfacher zu verstehen und zu debuggen
Auch In-Place-Modifikation ist ein Seiteneffekt – die nächsten Funktionen zeigen das:
sort() vs. sorted()
liste.sort()verändert die Liste direkt (in-place):namen = ["Zoe", "Anna", "Ben"] namen.sort() print(namen) # ["Anna", "Ben", "Zoe"]sorted(liste)erstellt eine neue Liste (Original bleibt unverändert):namen = ["Zoe", "Anna", "Ben"] sortiert = sorted(namen) print(namen) # ["Zoe", "Anna", "Ben"] - unverändert! print(sortiert) # ["Anna", "Ben", "Zoe"]Beide unterstützen
keyfür benutzerdefinierte Sortierung:sortiert = sorted(namen, key=len) # Nach Länge sortierenMerke:
sort()hat Seiteneffekt (ändert Original),sorted()ist eine reine Funktion (gibt neue Liste zurück).
map() und filter()
map()wendet eine Funktion auf jedes Element einer Sequenz an:namen = ["anna", "bob", "charlie"] # Traditionell mit Schleife gross = [] for name in namen: gross.append(name.upper()) # Mit map() - kompakter gross = map(str.upper, namen) print(gross) # <map object at ...> - ein Iterator! print(list(gross)) # ["ANNA", "BOB", "CHARLIE"]filter()behält nur Elemente, für die eine FunktionTruezurückgibt:zahlen = [1, -4, 7, 0, -3, 12] def ist_positiv(x): return x > 0 positiv = filter(ist_positiv, zahlen) print(list(positiv)) # [1, 7, 12]Wichtig: Beide geben einen Iterator zurück, keine Liste!
- Für eine Liste:
list(map(...))oderlist(filter(...)) - Ein Iterator kann nur einmal durchlaufen werden
mapped = map(str.upper, namen) for name in mapped: print(name) # Funktioniert for name in mapped: print(name) # Keine Ausgabe! Iterator ist "aufgebraucht"- Für eine Liste:
Hinweis: List Comprehensions (gleich) sind oft pythonischer als map/filter.
Lambda-Funktionen
Kurze, anonyme Funktionen für einfache Operationen:
# Herkömmliche Funktion def quadrat(x): return x * x # Gleiche Funktion als Lambda quadrat = lambda x: x * x # Praktisch als key-Funktion sortiert = sorted(teams, key=lambda x: len(x))Lambdas nur für einfache Ausdrücke verwenden – bei komplexer Logik besser eine normale Funktion definieren.
List Comprehensions
Eleganter Ersatz für Schleifen beim Erstellen von Listen:
# Traditionell quadrate = [] for n in range(5): quadrate.append(n * n) # Mit List Comprehension quadrate = [n * n for n in range(5)] # [0, 1, 4, 9, 16]Mit Filterbedingung:
# Nur gerade Zahlen quadrieren quadrate = [n * n for n in range(10) if n % 2 == 0] # [0, 4, 16, 36, 64]Syntax:
[ausdruck for element in sequenz if bedingung]Hinweis: Es gibt auch Dict Comprehensions (
{k: v for ...}) und Set Comprehensions ({x for ...}) sowie Generator Expressions ((x for ...)) für speichereffiziente Verarbeitung großer Datenmengen.
Zusammenfassend
In dieser Vorlesung haben wir die Python-Grundlagen aus der letzten Woche vertieft und fortgeschrittene Konzepte kennengelernt.
Konkret haben wir besprochen…
- Bibliotheken von Drittanbietern (pip, PyPI)
- Codequalität (Defensives Programmieren mit
isinstance(), Type Hints) - Kommandozeilen-Argumente und Exit-Codes
- OOP-Patterns: Vererbung mit
super(), Duck Typing - Collections vertieft: Listen, Tupel, Sets, Dictionaries, geschachtelte Strukturen
- JSON-Verarbeitung:
json.loads()undjson.load()für API-Daten und Config-Dateien - Seiteneffekte vs. reine Funktionen: Warum
sorted()oft besser ist alssort() - Funktionale Datenverarbeitung:
map(),filter(), Lambda-Funktionen - List Comprehensions: Elegante Alternative zu Schleifen
Bis zum nächsten Mal!