Knowledge Hub
FundamentalsIntermediateuebungen

Python Lösungen – Schritt für Schritt erklärt

Python Lösungen – Schritt für Schritt erklärt

Zu jeder Aufgabe: Vorgehen (wie du drangehst) + Lösung (Code). Tipp: Lies erst nur "Vorgehen", versuch es nochmal selbst, und schau erst danach in die Lösung.


1. Kleine Python-Programme

1.1 Nutzer begrüßen

Vorgehen: Eingabe mit input() holen, in Variable speichern, mit f-String ausgeben.

name = input("Wie heißt du? ")
print(f"Hallo {name}, schön dich kennenzulernen!")

1.2 Taschenrechner

Vorgehen: Zwei Zahlen + Operator einlesen (Achtung: input() liefert immer Text → mit float() umwandeln). Dann mit if/elif die passende Rechenart auswählen.

a = float(input("Erste Zahl: "))
b = float(input("Zweite Zahl: "))
op = input("Rechenart (+,-,*,/): ")

if op == "+":
    print(a + b)
elif op == "-":
    print(a - b)
elif op == "*":
    print(a * b)
elif op == "/":
    if b != 0:
        print(a / b)
    else:
        print("Division durch 0 nicht möglich")
else:
    print("Unbekannte Rechenart")

1.3 Primzahl prüfen

Vorgehen: Eine Zahl ist prim, wenn sie nur durch 1 und sich selbst teilbar ist. Prüfe alle Zahlen von 2 bis zahl-1 (oder bis zur Wurzel, effizienter). Wenn eine davon ohne Rest teilt → keine Primzahl.

def ist_primzahl(zahl):
    if zahl < 2:
        return False
    for i in range(2, zahl):
        if zahl % i == 0:
            return False
    return True

print(ist_primzahl(7))   # True
print(ist_primzahl(8))   # False

1.4 Zahlen erraten

Vorgehen: Zufallszahl mit random.randint() erzeugen. In einer while True-Schleife raten lassen, vergleichen, Hinweis geben, bei Treffer mit break beenden.

import random

zielzahl = random.randint(1, 100)

while True:
    versuch = int(input("Rate eine Zahl (1-100): "))
    if versuch < zielzahl:
        print("Höher!")
    elif versuch > zielzahl:
        print("Niedriger!")
    else:
        print("Richtig!")
        break

2. APIs

2.1 Nutzer von JSONPlaceholder holen

Vorgehen: requests.get(url) sendet die Anfrage. .json() wandelt die Antwort in Python-Datenstruktur (Liste von Dicts) um. Dann normal durchiterieren.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/users")
users = response.json()

for user in users:
    print(user["name"])

2.2 Wetterdaten (Open-Meteo, kein API-Key nötig)

Vorgehen: URL mit Koordinaten aufrufen, JSON auslesen. Prüfe die API-Doku für die genauen Feldnamen der Antwort.

import requests

lat, lon = 53.0793, 8.8017  # Bremen
url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current_weather=true"

response = requests.get(url)
data = response.json()

temperatur = data["current_weather"]["temperature"]
print(f"Aktuelle Temperatur: {temperatur}°C")

2.3 Zufallswitz-Funktion

Vorgehen: Funktion kapselt den API-Aufruf, damit man sie wiederverwenden kann. Setup + Punchline liegen als eigene Felder in der Antwort.

import requests

def hole_zufallswitz():
    response = requests.get("https://official-joke-api.appspot.com/random_joke")
    witz = response.json()
    return f"{witz['setup']} — {witz['punchline']}"

print(hole_zufallswitz())

2.4 API-Daten zusätzlich lokal speichern

Vorgehen: Ergebnis von .json() ist bereits eine Python-Liste/Dict → direkt mit json.dump() in Datei schreiben.

import requests
import json

response = requests.get("https://jsonplaceholder.typicode.com/users")
users = response.json()

with open("users.json", "w") as f:
    json.dump(users, f, indent=2)

print("Gespeichert!")

3. Daten verarbeiten

3.1 Summe, Durchschnitt, Min, Max ohne eingebaute Funktionen

Vorgehen: Mit einer Schleife durchlaufen, laufend vergleichen/addieren – wie die eingebauten Funktionen intern arbeiten.

zahlen = [4, 8, 2, 9, 5]

summe = 0
minimum = zahlen[0]
maximum = zahlen[0]

for z in zahlen:
    summe += z
    if z < minimum:
        minimum = z
    if z > maximum:
        maximum = z

durchschnitt = summe / len(zahlen)
print(summe, durchschnitt, minimum, maximum)

3.2 Längstes Wort + Anfangsbuchstaben zählen

Vorgehen: Längstes Wort: laufend vergleichen wie bei Max oben. Für die Zählung: Dictionary nutzen, .get(key, 0) um sicher hochzuzählen ohne KeyError.

woerter = ["Apfel", "Birne", "Ananas", "Kiwi"]

laengstes = woerter[0]
for w in woerter:
    if len(w) > len(laengstes):
        laengstes = w
print("Längstes Wort:", laengstes)

buchstaben_count = {}
for w in woerter:
    erster = w[0]
    buchstaben_count[erster] = buchstaben_count.get(erster, 0) + 1

print(buchstaben_count)

3.3 Produkte filtern & sortieren

Vorgehen: List Comprehension zum Filtern, sorted() mit key= um nach einem bestimmten Feld zu sortieren.

produkte = [
    {"name": "Kette", "preis": 25},
    {"name": "Ring", "preis": 60},
    {"name": "Armband", "preis": 45},
]

teure_produkte = [p for p in produkte if p["preis"] > 50]
sortiert = sorted(produkte, key=lambda p: p["preis"])

print(teure_produkte)
print(sortiert)

3.4 CSV einlesen & Umsatz berechnen

Vorgehen: csv.DictReader liest jede Zeile als Dictionary (Spaltennamen = Keys). Werte aus CSV sind immer Strings → für Berechnungen umwandeln.

import csv

umsatz_pro_produkt = {}

with open("verkaeufe.csv", newline="") as f:
    reader = csv.DictReader(f)
    for zeile in reader:
        produkt = zeile["Produkt"]
        menge = int(zeile["Menge"])
        preis = float(zeile["Preis"])
        umsatz = menge * preis
        umsatz_pro_produkt[produkt] = umsatz_pro_produkt.get(produkt, 0) + umsatz

print(umsatz_pro_produkt)

4. Dateien lesen/schreiben

4.1 Text in Datei schreiben

Vorgehen: open() mit Modus "w" (write) – überschreibt die Datei. with sorgt dafür, dass die Datei automatisch geschlossen wird.

def notiz_schreiben(text):
    with open("notizen.txt", "w") as f:
        f.write(text)

notiz_schreiben("Meine erste Notiz")

4.2 Datei zeilenweise lesen

Vorgehen: Modus "r" (read). Über die geöffnete Datei kann man direkt mit for iterieren, jede Zeile ist ein String.

def notiz_lesen():
    with open("notizen.txt", "r") as f:
        for zeile in f:
            print(zeile.strip())   # strip() entfernt den Zeilenumbruch

notiz_lesen()

4.3 Tagebuch mit Anhängen

Vorgehen: Modus "a" (append) fügt hinzu statt zu überschreiben. datetime für den Zeitstempel.

from datetime import datetime

def eintrag_hinzufuegen(text):
    zeitstempel = datetime.now().strftime("%Y-%m-%d %H:%M")
    with open("tagebuch.txt", "a") as f:
        f.write(f"[{zeitstempel}] {text}\n")

eintrag_hinzufuegen("Heute Python geübt.")

4.4 JSON schreiben & lesen

Vorgehen: json.dump() schreibt eine Python-Struktur als JSON-Datei, json.load() liest sie zurück in eine Python-Struktur.

import json

personen = [{"name": "Anna", "alter": 25}, {"name": "Ben", "alter": 30}]

with open("personen.json", "w") as f:
    json.dump(personen, f, indent=2)

with open("personen.json", "r") as f:
    geladen = json.load(f)

print(geladen)

5. Fehlerbehandlung

5.1 Erneut fragen bei ungültiger Zahl

Vorgehen: while True + try/except ValueError. Bei Erfolg mit break raus, bei Fehler Meldung + Schleife läuft weiter.

while True:
    try:
        zahl = int(input("Gib eine Zahl ein: "))
        break
    except ValueError:
        print("Das ist keine gültige Zahl, versuch's nochmal.")

print("Du hast eingegeben:", zahl)

5.2 Sichere Division

Vorgehen: try/except ZeroDivisionError gezielt für den einen Fehlerfall, den wir erwarten.

def sichere_division(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Fehler: Division durch 0 nicht möglich"

print(sichere_division(10, 2))
print(sichere_division(10, 0))

5.3 Datei-Fehler abfangen

Vorgehen: FileNotFoundError ist eine eigene, spezifischere Exception-Klasse – gezielt abfangen statt generisches except Exception.

try:
    with open("nicht_vorhanden.txt") as f:
        inhalt = f.read()
except FileNotFoundError:
    print("Die Datei wurde nicht gefunden.")

5.4 Eigene Exception-Klasse

Vorgehen: Eigene Exceptions erben von Exception. Mit raise wirfst du sie selbst, wenn eine Bedingung verletzt ist.

class UngueltigesAlterError(Exception):
    pass

def alter_setzen(alter):
    if alter < 0:
        raise UngueltigesAlterError("Alter darf nicht negativ sein")
    return alter

try:
    alter_setzen(-5)
except UngueltigesAlterError as e:
    print("Fehler:", e)

6. Funktionen und Klassen

6.1 Palindrom prüfen

Vorgehen: String umgedreht ([::-1]) mit Original vergleichen.

def ist_palindrom(text):
    text = text.lower()
    return text == text[::-1]

print(ist_palindrom("anna"))    # True
print(ist_palindrom("hallo"))   # False

6.2 Klasse Auto mit Altersberechnung

Vorgehen: Aktuelles Jahr über datetime.now().year holen, dann simple Subtraktion in einer Methode.

from datetime import datetime

class Auto:
    def __init__(self, marke, baujahr):
        self.marke = marke
        self.baujahr = baujahr

    def alter(self):
        return datetime.now().year - self.baujahr

mein_auto = Auto("BMW", 2018)
print(mein_auto.alter())

6.3 Klasse Warenkorb

Vorgehen: Liste als Attribut im Konstruktor anlegen. Methoden verändern diese Liste (append, remove) bzw. berechnen daraus (sum() über Preise).

class Warenkorb:
    def __init__(self):
        self.produkte = []

    def hinzufuegen(self, name, preis):
        self.produkte.append({"name": name, "preis": preis})

    def entfernen(self, name):
        self.produkte = [p for p in self.produkte if p["name"] != name]

    def gesamtpreis(self):
        return sum(p["preis"] for p in self.produkte)

korb = Warenkorb()
korb.hinzufuegen("Kette", 25)
korb.hinzufuegen("Ring", 40)
print(korb.gesamtpreis())   # 65

6.4 Vererbung: Mitarbeiter → Manager

Vorgehen: Manager erbt von Mitarbeiter mit class Manager(Mitarbeiter):. super().__init__() ruft den Konstruktor der Basisklasse auf, um Code nicht zu duplizieren.

class Mitarbeiter:
    def __init__(self, name, position):
        self.name = name
        self.position = position

class Manager(Mitarbeiter):
    def __init__(self, name, position, team):
        super().__init__(name, position)
        self.team = team   # Liste von Mitarbeiter-Objekten

    def team_anzeigen(self):
        for m in self.team:
            print(m.name)

ben = Mitarbeiter("Ben", "Entwickler")
chef = Manager("Nalan", "Teamleitung", [ben])
chef.team_anzeigen()

7. Listen/Dictionaries

7.1 Duplikate entfernen ohne set()

Vorgehen: Neue leere Liste, jedes Element nur hinzufügen, wenn es noch nicht drin ist.

def ohne_duplikate(liste):
    ergebnis = []
    for element in liste:
        if element not in ergebnis:
            ergebnis.append(element)
    return ergebnis

print(ohne_duplikate([1, 2, 2, 3, 1]))   # [1, 2, 3]

7.2 Liste umdrehen ohne .reverse()

Vorgehen: Rückwärts durch die Originalliste iterieren (Index von hinten) und in neue Liste anhängen.

def liste_umdrehen(liste):
    ergebnis = []
    for i in range(len(liste) - 1, -1, -1):
        ergebnis.append(liste[i])
    return ergebnis

print(liste_umdrehen([1, 2, 3]))   # [3, 2, 1]

7.3 Zwei Listen zu Dictionary

Vorgehen: zip() kombiniert zwei Listen elementweise zu Paaren, dict() wandelt das direkt in ein Dictionary um.

namen = ["Anna", "Ben", "Cem"]
alter = [25, 30, 22]

personen = dict(zip(namen, alter))
print(personen)   # {'Anna': 25, 'Ben': 30, 'Cem': 22}

7.4 Wörter nach Länge gruppieren

Vorgehen: Für jedes Wort die Länge als Key nutzen, mit .setdefault() eine leere Liste anlegen falls noch nicht vorhanden, dann anhängen.

woerter = ["Hut", "Apfel", "Bus", "Sonne"]

gruppiert = {}
for w in woerter:
    laenge = len(w)
    gruppiert.setdefault(laenge, []).append(w)

print(gruppiert)   # {3: ['Hut', 'Bus'], 5: ['Apfel', 'Sonne']}

8. Einfache Algorithmen

8.1 Bubble Sort

Vorgehen: Wiederholt benachbarte Elemente vergleichen und tauschen, wenn sie in falscher Reihenfolge sind. Nach jedem Durchlauf "sinkt" das größte verbleibende Element ans Ende.

def bubble_sort(liste):
    n = len(liste)
    for i in range(n):
        for j in range(n - i - 1):
            if liste[j] > liste[j + 1]:
                liste[j], liste[j + 1] = liste[j + 1], liste[j]
    return liste

print(bubble_sort([5, 2, 9, 1, 5]))

8.2 Binäre Suche

Vorgehen: Nur auf sortierten Listen. Immer die Mitte prüfen, dann je nach Vergleich nur noch die linke oder rechte Hälfte weiter durchsuchen.

def binaere_suche(liste, ziel):
    links, rechts = 0, len(liste) - 1
    while links <= rechts:
        mitte = (links + rechts) // 2
        if liste[mitte] == ziel:
            return mitte
        elif liste[mitte] < ziel:
            links = mitte + 1
        else:
            rechts = mitte - 1
    return -1

print(binaere_suche([1, 3, 5, 7, 9], 7))   # 3

8.3 Fibonacci-Folge

Vorgehen: Start mit den ersten zwei Werten (0, 1), jede weitere Zahl ist die Summe der letzten zwei.

def fibonacci(n):
    folge = [0, 1]
    while len(folge) < n:
        folge.append(folge[-1] + folge[-2])
    return folge[:n]

print(fibonacci(8))   # [0, 1, 1, 2, 3, 5, 8, 13]

8.4 Anagramm prüfen

Vorgehen: Zwei Wörter sind Anagramme, wenn sie – sortiert – identisch sind.

def sind_anagramme(wort1, wort2):
    return sorted(wort1.lower()) == sorted(wort2.lower())

print(sind_anagramme("Ohr", "Roh"))   # True

8.5 Fakultät (rekursiv)

Vorgehen: Rekursion braucht einen Basisfall (hier: 0! = 1), sonst läuft sie endlos. Jeder Aufruf ruft sich selbst mit kleinerem Wert auf, bis der Basisfall erreicht ist.

def fakultaet(n):
    if n == 0:
        return 1
    return n * fakultaet(n - 1)

print(fakultaet(5))   # 120

9. Datenbanken (SQLite)

9.1 Datenbank + Tabelle erstellen

Vorgehen: sqlite3.connect() erstellt die Datei automatisch, falls sie nicht existiert. SQL-Befehle werden als String über .execute() gesendet.

import sqlite3

verbindung = sqlite3.connect("datenbank.db")
cursor = verbindung.cursor()

cursor.execute("""
    CREATE TABLE IF NOT EXISTS personen (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        alter_ INTEGER
    )
""")
verbindung.commit()

9.2 Person hinzufügen

Vorgehen: ?-Platzhalter statt Werte direkt in den String einzufügen (verhindert SQL-Injection). Nach INSERT immer commit() nicht vergessen.

def person_hinzufuegen(name, alter):
    cursor.execute("INSERT INTO personen (name, alter_) VALUES (?, ?)", (name, alter))
    verbindung.commit()

person_hinzufuegen("Nalan", 30)

9.3 Alle Personen anzeigen

Vorgehen: SELECT liefert die Daten, .fetchall() holt alle Ergebniszeilen als Liste von Tupeln.

def alle_personen_anzeigen():
    cursor.execute("SELECT * FROM personen")
    for zeile in cursor.fetchall():
        print(zeile)

alle_personen_anzeigen()

9.4 Löschen & Aktualisieren

def person_loeschen(id):
    cursor.execute("DELETE FROM personen WHERE id = ?", (id,))
    verbindung.commit()

def person_aktualisieren(id, neuer_name):
    cursor.execute("UPDATE personen SET name = ? WHERE id = ?", (neuer_name, id))
    verbindung.commit()

9.5 Verknüpfte Tabelle mit JOIN

Vorgehen: Fremdschlüssel = Spalte, die auf die id einer anderen Tabelle verweist. JOIN verbindet beide Tabellen über diese Spalte.

cursor.execute("""
    CREATE TABLE IF NOT EXISTS bestellungen (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        person_id INTEGER,
        produkt TEXT,
        FOREIGN KEY (person_id) REFERENCES personen (id)
    )
""")
verbindung.commit()

# Bestellungen einer Person abfragen
cursor.execute("""
    SELECT personen.name, bestellungen.produkt
    FROM bestellungen
    JOIN personen ON bestellungen.person_id = personen.id
    WHERE personen.id = ?
""", (1,))

for zeile in cursor.fetchall():
    print(zeile)

10. Kleine Webanwendungen (FastAPI)

10.1 Einfacher Endpunkt

Vorgehen: @app.get("/pfad") registriert eine Route. Die Funktion darunter läuft, wenn diese URL aufgerufen wird. Rückgabe wird automatisch zu JSON.

from fastapi import FastAPI

app = FastAPI()

@app.get("/hallo/{name}")
def hallo(name: str):
    return {"message": f"Hallo, {name}!"}

# Starten mit: uvicorn main:app --reload

10.2 Todo-API (im Speicher)

Vorgehen: Einfache Python-Liste als "Datenbank-Ersatz". POST nimmt Daten über den Request-Body entgegen (mit Pydantic-Modell definiert).

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()
todos = []

class Todo(BaseModel):
    titel: str
    erledigt: bool = False

@app.get("/todos")
def alle_todos():
    return todos

@app.post("/todos")
def todo_hinzufuegen(todo: Todo):
    todos.append(todo)
    return {"status": "hinzugefügt"}

10.3 Mit SQLite verbinden

Vorgehen: Statt der Liste todos nutzt du die Funktionen aus Aufgabe 9 (mit einer todos-Tabelle statt personen), um bei jedem Request in die Datenbank zu schreiben/lesen.

import sqlite3
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

def get_connection():
    return sqlite3.connect("todos.db")

@app.on_event("startup")
def setup():
    conn = get_connection()
    conn.execute("""
        CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            titel TEXT,
            erledigt INTEGER DEFAULT 0
        )
    """)
    conn.commit()
    conn.close()

class Todo(BaseModel):
    titel: str

@app.post("/todos")
def todo_hinzufuegen(todo: Todo):
    conn = get_connection()
    conn.execute("INSERT INTO todos (titel) VALUES (?)", (todo.titel,))
    conn.commit()
    conn.close()
    return {"status": "gespeichert"}

10.4 Als erledigt markieren

Vorgehen: PATCH/PUT mit der id als URL-Parameter, UPDATE-Statement in der Datenbank.

@app.patch("/todos/{id}")
def todo_erledigt(id: int):
    conn = get_connection()
    conn.execute("UPDATE todos SET erledigt = 1 WHERE id = ?", (id,))
    conn.commit()
    conn.close()
    return {"status": "aktualisiert"}

10.5 Einfaches HTML-Frontend

Vorgehen: fetch() im Browser ruft deine FastAPI-Endpunkte auf, .json() verarbeitet die Antwort, .map()/Schleife baut daraus HTML.

<div id="liste"></div>

<script>
  fetch("http://localhost:8000/todos")
    .then(response => response.json())
    .then(todos => {
      const container = document.getElementById("liste");
      todos.forEach(todo => {
        const p = document.createElement("p");
        p.textContent = todo.titel;
        container.appendChild(p);
      });
    });
</script>

11. Debugging

11.1 Fehler bewusst einbauen und lesen

Vorgehen: Python-Fehlermeldungen lesen sich von unten nach oben: die letzte Zeile sagt dir den Fehlertyp + was genau falsch ist, die Zeilen darüber zeigen dir wo im Code (Traceback).

def begruessung(nam):   # Tippfehler: sollte 'name' heißen
    return f"Hallo {name}"   # NameError: name 'name' is not defined

→ Die Fehlermeldung nennt exakt Zeile und Variable. Erste Anlaufstelle bei jedem Fehler.

11.2 Print-Debugging

Vorgehen: Gezielt an "verdächtigen" Stellen den Wert einer Variable ausgeben, um zu sehen, ob sie das enthält, was du erwartest.

def summe_liste(liste):
    summe = 0
    for zahl in liste:
        print("aktuelle Zahl:", zahl, "| Summe bisher:", summe)   # Debug-Ausgabe
        summe += zahl
    return summe

11.3 Python-Debugger (breakpoint())

Vorgehen: breakpoint() pausiert das Programm an genau dieser Stelle. Im Terminal kannst du dann interaktiv Variablen inspizieren.

def berechne(a, b):
    ergebnis = a * b
    breakpoint()   # Programm hält hier an
    return ergebnis + 10

berechne(3, 4)

Im Debugger-Prompt: p ergebnis (Wert anzeigen), n (nächste Zeile), c (weiterlaufen lassen).

11.4 Fehler nur durch Lesen finden

Vorgehen: Code Zeile für Zeile durchgehen und mit Papier/Kopf "simulieren", welchen Wert jede Variable zu jedem Zeitpunkt hat – ohne ihn auszuführen.

def bubble_sort(liste):
    n = len(liste)
    for i in range(n):
        for j in range(n - i):        # Fehler: sollte range(n - i - 1) sein
            if liste[j] > liste[j + 1]:   # → IndexError bei letztem Durchlauf
                liste[j], liste[j + 1] = liste[j + 1], liste[j]
    return liste

→ Bei j = n - i - 1 (letzter Wert der Range) wird auf liste[j + 1] zugegriffen, was aber außerhalb der Liste liegt. Klassischer Off-by-one-Fehler.


Merksatz zum Vorgehen bei jeder neuen Aufgabe

  1. Verstehen: Was soll am Ende rauskommen? (Beispiel-Input/Output aufschreiben)
  2. Zerlegen: In welche kleineren Schritte lässt sich das aufteilen?
  3. Grob skizzieren: Pseudocode oder Kommentare für jeden Schritt
  4. Schritt für Schritt coden und nach jedem Schritt mit print() testen
  5. Erst am Ende auf saubere/kurze Lösung optimieren

Wenn du bei einer der Lösungen etwas nicht verstehst oder es lieber gemeinsam nochmal Zeile für Zeile durchgehen willst, sag einfach welche.