Initialisiere didaktische Python OOP-Übungsreihe für Nachhilfe (Kapitel 01 bis 06)
This commit is contained in:
commit
ce35e7d479
28 changed files with 2166 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.env
|
||||
venv/
|
||||
.venv/
|
||||
.DS_Store
|
||||
86
01_einstieg_klassen/README.md
Normal file
86
01_einstieg_klassen/README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Kapitel 01: Die erste Klasse – Was sind Klassen & Objekte?
|
||||
|
||||
Herzlich willkommen in der Welt der objektorientierten Programmierung (kurz: **OOP**)! 🐶🐱
|
||||
|
||||
Bisher hast du in Python mit Variablen, Listen, Dictionaries und Funktionen gearbeitet. Das hat super funktioniert! Doch wenn Programme größer werden, wird es schnell unübersichtlich.
|
||||
|
||||
Hier kommen **Klassen** ins Spiel.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Die wichtigste Analogie: Bauplan vs. Objekt
|
||||
|
||||
Stell dir vor, du bist Architektin oder Bäckermeisterin:
|
||||
- Eine **Klasse** ist wie ein **Bauplan** für ein Haus (oder eine **Plätzchen-Ausstechform**). Der Bauplan selbst ist noch kein Haus, in dem man wohnen kann. Er beschreibt nur: *Wie sieht ein Haus aus? Welche Eigenschaften hat es?*
|
||||
- Ein **Objekt** (auch **Instanz** genannt) ist das **echte Haus**, das nach diesem Bauplan gebaut wurde (oder das gebackene Plätzchen). Aus *einem* Bauplan kannst du beliebig viele verschiedene Häuser bauen!
|
||||
|
||||
```
|
||||
[ KLASSE / BAUPLAN ]
|
||||
class Haustier:
|
||||
│
|
||||
─────────┴─────────
|
||||
│ │
|
||||
[ OBJEKT 1 ] [ OBJEKT 2 ]
|
||||
Bello, Hund, 3 Mimi, Katze, 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Warum nicht einfach Dictionaries?
|
||||
|
||||
Bisher hättest du Tiere vielleicht so gespeichert:
|
||||
```python
|
||||
tier1 = {"name": "Bello", "tierart": "Hund", "alter": 3}
|
||||
tier2 = {"name": "Mimi", "art": "Katze", "alter": 5} # Ups! Hier heißt der Key "art" statt "tierart"!
|
||||
```
|
||||
Beim Dictionary kann man sich schnell bei Schlüsseln vertippen. Eine Klasse gibt uns eine feste Struktur vor.
|
||||
|
||||
---
|
||||
|
||||
## 💻 Wie sieht eine Klasse in Python aus?
|
||||
|
||||
Eine einfache Klasse definierst du mit dem Schlüsselwort `class`:
|
||||
|
||||
```python
|
||||
class Auto:
|
||||
pass # 'pass' bedeutet: "Hier passiert erst mal nichts"
|
||||
```
|
||||
|
||||
Aus dieser Klasse kannst du nun ein Objekt erzeugen (**Instanziierung**):
|
||||
|
||||
```python
|
||||
# Objekt erstellen:
|
||||
mein_auto = Auto()
|
||||
|
||||
# Eigenschaften (Attribute) zuweisen mit dem Punkt (.):
|
||||
mein_auto.farbe = "Rot"
|
||||
mein_auto.marke = "VW"
|
||||
mein_auto.ps = 110
|
||||
|
||||
print(mein_auto.farbe) # Gibt "Rot" aus
|
||||
```
|
||||
|
||||
Du kannst ganz einfach ein zweites Auto bauen:
|
||||
```python
|
||||
dein_auto = Auto()
|
||||
dein_auto.farbe = "Blau"
|
||||
dein_auto.marke = "BMW"
|
||||
dein_auto.ps = 180
|
||||
```
|
||||
`mein_auto` und `dein_auto` sind zwei völlig eigenständige Objekte, die aus demselben Bauplan `Auto` entstanden sind!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe
|
||||
|
||||
Öffne jetzt die Datei `aufgabe.py` und bearbeite die Aufgaben:
|
||||
1. Erstelle die Klasse `Haustier`.
|
||||
2. Erzeuge zwei Haustier-Objekte `bello` und `mimi` mit Eigenschaften.
|
||||
3. Schreibe zwei kleine Hilfsfunktionen, die mit deinen Haustier-Objekten arbeiten.
|
||||
|
||||
### Testen:
|
||||
Führe deine Tests aus mit:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
Sobald alle Tests bestanden sind, kannst du mit Kapitel 02 weitermachen!
|
||||
94
01_einstieg_klassen/aufgabe.py
Normal file
94
01_einstieg_klassen/aufgabe.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
Kapitel 01: Die erste Klasse – Aufgabenblatt
|
||||
============================================
|
||||
|
||||
Bearbeite die Aufgaben Schritt für Schritt von TODO 1 bis TODO 5.
|
||||
Wenn du fertig bist, überprüfe deine Lösung mit:
|
||||
python3 test_aufgabe.py
|
||||
"""
|
||||
|
||||
# ==============================================================================
|
||||
# TODO 1: Definiere eine leere Klasse namens "Haustier".
|
||||
# Tipp: Verwende das Schlüsselwort 'class' und vorerst 'pass' im Klassenrumpf.
|
||||
# ==============================================================================
|
||||
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# TODO 2: Erstelle eine Funktion namens "erstelle_bello()".
|
||||
# Diese Funktion soll:
|
||||
# 1. Ein neues Haustier-Objekt erzeugen.
|
||||
# 2. Dem Objekt folgende Attribute zuweisen:
|
||||
# - name: "Bello"
|
||||
# - tierart: "Hund"
|
||||
# - alter: 3
|
||||
# 3. Das fertige Objekt zurückgeben (return).
|
||||
# ==============================================================================
|
||||
|
||||
def erstelle_bello():
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# TODO 3: Erstelle eine Funktion namens "erstelle_mimi()".
|
||||
# Diese Funktion soll:
|
||||
# 1. Ein neues Haustier-Objekt erzeugen.
|
||||
# 2. Dem Objekt folgende Attribute zuweisen:
|
||||
# - name: "Mimi"
|
||||
# - tierart: "Katze"
|
||||
# - alter: 5
|
||||
# 3. Das fertige Objekt zurückgeben (return).
|
||||
# ==============================================================================
|
||||
|
||||
def erstelle_mimi():
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# TODO 4: Schreibe eine Funktion "steckbrief_text(haustier)".
|
||||
# Die Funktion bekommt ein Haustier-Objekt als Parameter übergeben.
|
||||
# Sie soll einen formatierten Text im folgenden Format zurückgeben:
|
||||
# "{name} ist ein(e) {tierart} und ist {alter} Jahre alt."
|
||||
#
|
||||
# Beispiel:
|
||||
# Wenn bello übergeben wird, soll zurückgegeben werden:
|
||||
# "Bello ist ein(e) Hund und ist 3 Jahre alt."
|
||||
# ==============================================================================
|
||||
|
||||
def steckbrief_text(haustier):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# TODO 5: Schreibe eine Funktion "aelteres_tier(tier1, tier2)".
|
||||
# Die Funktion bekommt zwei Haustier-Objekte übergeben.
|
||||
# Sie soll dasjenige Haustier-Objekt zurückgeben, dessen 'alter' größer ist.
|
||||
# Wenn beide Tiere gleich alt sind, soll tier1 zurückgegeben werden.
|
||||
# ==============================================================================
|
||||
|
||||
def aelteres_tier(tier1, tier2):
|
||||
# Schreibe hier deinen Code für TODO 5:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Hauptprogramm zum Ausprobieren:
|
||||
# (Du kannst diese Datei direkt mit 'python3 aufgabe.py' ausführen)
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
print("--- Teste deine Haustiere ---")
|
||||
bello = erstelle_bello()
|
||||
mimi = erstelle_mimi()
|
||||
|
||||
if bello and mimi:
|
||||
print(steckbrief_text(bello))
|
||||
print(steckbrief_text(mimi))
|
||||
aelter = aelteres_tier(bello, mimi)
|
||||
print(f"Das ältere Tier ist: {aelter.name}")
|
||||
else:
|
||||
print("Hinweis: Implementiere erst die TODOs, um die Ausgabe zu sehen!")
|
||||
49
01_einstieg_klassen/musterloesung.py
Normal file
49
01_einstieg_klassen/musterloesung.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""
|
||||
Kapitel 01: Die erste Klasse – Musterlösung
|
||||
===========================================
|
||||
Hier findest du die vollständige Lösung mit Erklärungen.
|
||||
"""
|
||||
|
||||
# TODO 1: Die Klasse Haustier definieren
|
||||
class Haustier:
|
||||
pass
|
||||
|
||||
|
||||
# TODO 2: Ein Haustier-Objekt namens Bello erstellen und Attribute zuweisen
|
||||
def erstelle_bello():
|
||||
tier = Haustier()
|
||||
tier.name = "Bello"
|
||||
tier.tierart = "Hund"
|
||||
tier.alter = 3
|
||||
return tier
|
||||
|
||||
|
||||
# TODO 3: Ein Haustier-Objekt namens Mimi erstellen
|
||||
def erstelle_mimi():
|
||||
tier = Haustier()
|
||||
tier.name = "Mimi"
|
||||
tier.tierart = "Katze"
|
||||
tier.alter = 5
|
||||
return tier
|
||||
|
||||
|
||||
# TODO 4: Steckbrief generieren
|
||||
def steckbrief_text(haustier):
|
||||
return f"{haustier.name} ist ein(e) {haustier.tierart} und ist {haustier.alter} Jahre alt."
|
||||
|
||||
|
||||
# TODO 5: Das ältere Tier ermitteln
|
||||
def aelteres_tier(tier1, tier2):
|
||||
if tier2.alter > tier1.alter:
|
||||
return tier2
|
||||
else:
|
||||
return tier1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bello = erstelle_bello()
|
||||
mimi = erstelle_mimi()
|
||||
print(steckbrief_text(bello))
|
||||
print(steckbrief_text(mimi))
|
||||
aelter = aelteres_tier(bello, mimi)
|
||||
print(f"Das ältere Tier ist: {aelter.name}")
|
||||
84
01_einstieg_klassen/test_aufgabe.py
Normal file
84
01_einstieg_klassen/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Sicherstellen, dass das aktuelle Verzeichnis im Modulpfad ist
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel01(unittest.TestCase):
|
||||
|
||||
def test_01_klasse_existiert(self):
|
||||
"""Prüft, ob die Klasse Haustier definiert wurde."""
|
||||
self.assertTrue(
|
||||
hasattr(aufgabe, "Haustier"),
|
||||
"Fehler: Die Klasse 'Haustier' existiert noch nicht. Nutze: class Haustier:"
|
||||
)
|
||||
self.assertTrue(
|
||||
isinstance(aufgabe.Haustier, type),
|
||||
"Fehler: 'Haustier' sollte eine Klasse sein!"
|
||||
)
|
||||
|
||||
def test_02_erstelle_bello(self):
|
||||
"""Prüft, ob erstelle_bello() ein korrektes Haustier-Objekt liefert."""
|
||||
self.assertTrue(hasattr(aufgabe, "erstelle_bello"), "Fehler: Funktion 'erstelle_bello' fehlt!")
|
||||
bello = aufgabe.erstelle_bello()
|
||||
self.assertIsNotNone(bello, "Fehler: 'erstelle_bello()' gibt None zurück. Vergiss nicht das 'return'!")
|
||||
self.assertIsInstance(bello, aufgabe.Haustier, "Fehler: 'bello' muss eine Instanz der Klasse 'Haustier' sein!")
|
||||
self.assertEqual(getattr(bello, "name", None), "Bello", "Fehler: 'bello.name' muss 'Bello' sein!")
|
||||
self.assertEqual(getattr(bello, "tierart", None), "Hund", "Fehler: 'bello.tierart' muss 'Hund' sein!")
|
||||
self.assertEqual(getattr(bello, "alter", None), 3, "Fehler: 'bello.alter' muss 3 sein!")
|
||||
|
||||
def test_03_erstelle_mimi(self):
|
||||
"""Prüft, ob erstelle_mimi() ein korrektes Haustier-Objekt liefert."""
|
||||
self.assertTrue(hasattr(aufgabe, "erstelle_mimi"), "Fehler: Funktion 'erstelle_mimi' fehlt!")
|
||||
mimi = aufgabe.erstelle_mimi()
|
||||
self.assertIsNotNone(mimi, "Fehler: 'erstelle_mimi()' gibt None zurück. Vergiss nicht das 'return'!")
|
||||
self.assertIsInstance(mimi, aufgabe.Haustier, "Fehler: 'mimi' muss eine Instanz der Klasse 'Haustier' sein!")
|
||||
self.assertEqual(getattr(mimi, "name", None), "Mimi", "Fehler: 'mimi.name' muss 'Mimi' sein!")
|
||||
self.assertEqual(getattr(mimi, "tierart", None), "Katze", "Fehler: 'mimi.tierart' muss 'Katze' sein!")
|
||||
self.assertEqual(getattr(mimi, "alter", None), 5, "Fehler: 'mimi.alter' muss 5 sein!")
|
||||
|
||||
def test_04_steckbrief_text(self):
|
||||
"""Prüft, ob steckbrief_text() den richtigen String formatiert."""
|
||||
self.assertTrue(hasattr(aufgabe, "steckbrief_text"), "Fehler: Funktion 'steckbrief_text' fehlt!")
|
||||
tier = aufgabe.Haustier()
|
||||
tier.name = "Rocky"
|
||||
tier.tierart = "Papagei"
|
||||
tier.alter = 2
|
||||
|
||||
ergebnis = aufgabe.steckbrief_text(tier)
|
||||
erwartet = "Rocky ist ein(e) Papagei und ist 2 Jahre alt."
|
||||
self.assertEqual(
|
||||
ergebnis,
|
||||
erwartet,
|
||||
f"Fehler: Textausgabe weicht ab!\nErwartet: '{erwartet}'\nErhalten: '{ergebnis}'"
|
||||
)
|
||||
|
||||
def test_05_aelteres_tier(self):
|
||||
"""Prüft die Altersvergleich-Funktion."""
|
||||
self.assertTrue(hasattr(aufgabe, "aelteres_tier"), "Fehler: Funktion 'aelteres_tier' fehlt!")
|
||||
t1 = aufgabe.Haustier()
|
||||
t1.name = "Tier A"
|
||||
t1.alter = 4
|
||||
|
||||
t2 = aufgabe.Haustier()
|
||||
t2.name = "Tier B"
|
||||
t2.alter = 7
|
||||
|
||||
t3 = aufgabe.Haustier()
|
||||
t3.name = "Tier C"
|
||||
t3.alter = 4
|
||||
|
||||
self.assertIs(aufgabe.aelteres_tier(t1, t2), t2, "Fehler: Tier B (7) ist älter als Tier A (4)!")
|
||||
self.assertIs(aufgabe.aelteres_tier(t2, t1), t2, "Fehler: Tier B (7) ist älter als Tier A (4)!")
|
||||
self.assertIs(aufgabe.aelteres_tier(t1, t3), t1, "Fehler: Bei gleichem Alter soll tier1 zurückgegeben werden!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
90
02_init_und_self/README.md
Normal file
90
02_init_und_self/README.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Kapitel 02: Der Konstruktor `__init__` und das geheimnisvolle `self` 💳
|
||||
|
||||
Im ersten Kapitel haben wir Attribute noch manuell nach dem Erstellen zugewiesen:
|
||||
```python
|
||||
bello = Haustier()
|
||||
bello.name = "Bello"
|
||||
bello.alter = 3
|
||||
```
|
||||
Das ist mühsam und fehleranfällig: Was passiert, wenn man vergisst, `.alter` zu setzen? Richtig: Das Programm stürzt ab, sobald jemand darauf zugreift!
|
||||
|
||||
Deshalb gibt es in Python den **Konstruktor**: `__init__`.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Was ist `__init__`?
|
||||
|
||||
`__init__` steht für *initialisieren* (vorbereiten/startklar machen).
|
||||
|
||||
Es ist eine **spezielle Methode**, die Python **automatisch aufruft**, sobald du ein neues Objekt erstellst:
|
||||
|
||||
```python
|
||||
class Haustier:
|
||||
def __init__(self, name, tierart, alter):
|
||||
self.name = name
|
||||
self.tierart = tierart
|
||||
self.alter = alter
|
||||
|
||||
# Jetzt können wir die Werte direkt in die Klammern schreiben!
|
||||
bello = Haustier("Bello", "Hund", 3)
|
||||
mimi = Haustier("Mimi", "Katze", 5)
|
||||
|
||||
print(bello.name) # Gibt sofort "Bello" aus
|
||||
```
|
||||
Die doppelten Unterstriche `__` nennt man in Python auch **Dunder** (Double Underscore). Sie signalisieren besondere Python-Funktionen.
|
||||
|
||||
---
|
||||
|
||||
## 🪞 Was bedeutet eigentlich `self`?
|
||||
|
||||
`self` verwirrt am Anfang fast jeden – ist aber ganz einfach:
|
||||
- `self` bedeutet auf Deutsch **"ich selbst"** oder **"dieses Objekt hier"**.
|
||||
- Wenn du `bello = Haustier("Bello", "Hund", 3)` aufrufst, übergibt Python das neu entstandene `bello`-Objekt automatisch als erstes Argument `self`.
|
||||
- `self.name = name` bedeutet also: *"Speichere den übergebenen Namen in MEINEM EIGENEN Attribut `.name` ab!"*
|
||||
|
||||
```
|
||||
Haustier("Bello", "Hund", 3)
|
||||
│
|
||||
▼
|
||||
def __init__(self, name, tierart, alter):
|
||||
▲ │ │ │
|
||||
│ ▼ ▼ ▼
|
||||
bello "Bello" "Hund" 3
|
||||
│
|
||||
├── bello.name = "Bello"
|
||||
├── bello.tierart = "Hund"
|
||||
└── bello.alter = 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Standardwerte (Default Parameters)
|
||||
|
||||
Manchmal hat ein Attribut einen typischen Startwert (z.B. ein neues Bankkonto startet meist bei `0.0` Euro):
|
||||
|
||||
```python
|
||||
class Bankkonto:
|
||||
def __init__(self, inhaber, kontostand=0.0):
|
||||
self.inhaber = inhaber
|
||||
self.kontostand = kontostand
|
||||
|
||||
# Konto mit 0.0 Euro Startguthaben:
|
||||
k1 = Bankkonto("Anna")
|
||||
|
||||
# Konto mit speziellem Startguthaben:
|
||||
k2 = Bankkonto("Ben", 50.0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe: Das Bankkonto
|
||||
|
||||
Öffne `aufgabe.py`. Du programmierst eine Klasse `Bankkonto`:
|
||||
1. Schreibe den Konstruktor `__init__`.
|
||||
2. Ergänze Methoden zum Einzahlen (`einzahlen`) und Auszahlen (`auszahlen`), die darauf achten, dass man nicht ins Minus rutscht.
|
||||
3. Ergänze eine Methode `info_text()`.
|
||||
|
||||
### Testen:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
77
02_init_und_self/aufgabe.py
Normal file
77
02_init_und_self/aufgabe.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
Kapitel 02: Der Konstruktor __init__ und self
|
||||
==============================================
|
||||
|
||||
In dieser Aufgabe erstellst du eine Klasse "Bankkonto" für eine Taschengeld-App.
|
||||
"""
|
||||
|
||||
class Bankkonto:
|
||||
# ==========================================================================
|
||||
# TODO 1: Schreibe die __init__-Methode.
|
||||
# Parameter: self, inhaber (String), kontostand (Float, Standardwert 0.0)
|
||||
#
|
||||
# Speichere die Parameter als Attribute an 'self':
|
||||
# - self.inhaber
|
||||
# - self.kontostand
|
||||
# ==========================================================================
|
||||
def __init__(self, inhaber, kontostand=0.0):
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 2: Schreibe die Methode "einzahlen(self, betrag)".
|
||||
# Regeln:
|
||||
# 1. Wenn 'betrag' größer als 0 ist:
|
||||
# - Erhöhe 'self.kontostand' um 'betrag'.
|
||||
# - Gib True zurück (Einzahlung erfolgreich).
|
||||
# 2. Wenn 'betrag' kleiner oder gleich 0 ist:
|
||||
# - Verändere den Kontostand nicht.
|
||||
# - Gib False zurück (ungültiger Betrag).
|
||||
# ==========================================================================
|
||||
def einzahlen(self, betrag):
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 3: Schreibe die Methode "auszahlen(self, betrag)".
|
||||
# Regeln:
|
||||
# 1. Der Betrag muss größer als 0 sein UND es muss genug Geld auf dem Konto
|
||||
# sein (self.kontostand >= betrag).
|
||||
# - Ziehe 'betrag' von 'self.kontostand' ab.
|
||||
# - Gib True zurück (Auszahlung erfolgreich).
|
||||
# 2. Andernfalls (nicht genug Geld oder ungültiger Betrag):
|
||||
# - Verändere den Kontostand nicht.
|
||||
# - Gib False zurück.
|
||||
# ==========================================================================
|
||||
def auszahlen(self, betrag):
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 4: Schreibe die Methode "info_text(self)".
|
||||
# Sie soll einen String im folgenden Format zurückgeben:
|
||||
# "Konto von {inhaber}: {kontostand:.2f} Euro"
|
||||
#
|
||||
# Beispiel: Bei inhaber="Mia" und kontostand=25.5 -> "Konto von Mia: 25.50 Euro"
|
||||
# ==========================================================================
|
||||
def info_text(self):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Hauptprogramm zum Ausprobieren:
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
print("--- Teste dein Bankkonto ---")
|
||||
konto = Bankkonto("Mia", 20.0)
|
||||
print(konto.info_text())
|
||||
|
||||
erfolg = konto.einzahlen(15.5)
|
||||
print(f"15.50€ eingezahlt? {erfolg} -> Neuer Stand: {konto.kontostand}€")
|
||||
|
||||
erfolg = konto.auszahlen(50.0)
|
||||
print(f"50.00€ ausgezahlt? {erfolg} (sollte False sein) -> Stand: {konto.kontostand}€")
|
||||
|
||||
erfolg = konto.auszahlen(10.0)
|
||||
print(f"10.00€ ausgezahlt? {erfolg} -> Stand: {konto.kontostand}€")
|
||||
37
02_init_und_self/musterloesung.py
Normal file
37
02_init_und_self/musterloesung.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
Kapitel 02: Der Konstruktor __init__ und self – Musterlösung
|
||||
=============================================================
|
||||
"""
|
||||
|
||||
class Bankkonto:
|
||||
# TODO 1: Konstruktor
|
||||
def __init__(self, inhaber, kontostand=0.0):
|
||||
self.inhaber = inhaber
|
||||
self.kontostand = float(kontostand)
|
||||
|
||||
# TODO 2: Einzahlen mit Betragsüberprüfung
|
||||
def einzahlen(self, betrag):
|
||||
if betrag > 0:
|
||||
self.kontostand += betrag
|
||||
return True
|
||||
return False
|
||||
|
||||
# TODO 3: Auszahlen mit Guthabenüberprüfung
|
||||
def auszahlen(self, betrag):
|
||||
if betrag > 0 and self.kontostand >= betrag:
|
||||
self.kontostand -= betrag
|
||||
return True
|
||||
return False
|
||||
|
||||
# TODO 4: Formatierter Info-Text
|
||||
def info_text(self):
|
||||
return f"Konto von {self.inhaber}: {self.kontostand:.2f} Euro"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
konto = Bankkonto("Mia", 20.0)
|
||||
print(konto.info_text())
|
||||
konto.einzahlen(15.5)
|
||||
print(konto.info_text())
|
||||
konto.auszahlen(10.0)
|
||||
print(konto.info_text())
|
||||
85
02_init_und_self/test_aufgabe.py
Normal file
85
02_init_und_self/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel02(unittest.TestCase):
|
||||
|
||||
def test_01_init_standardwert(self):
|
||||
"""Prüft __init__ mit Standardwert für kontostand."""
|
||||
konto = aufgabe.Bankkonto("Lukas")
|
||||
self.assertEqual(konto.inhaber, "Lukas", "Fehler: self.inhaber wurde nicht korrekt gesetzt!")
|
||||
self.assertEqual(konto.kontostand, 0.0, "Fehler: Standardwert für kontostand sollte 0.0 sein!")
|
||||
|
||||
def test_02_init_mit_startguthaben(self):
|
||||
"""Prüft __init__ mit übergebenem Startguthaben."""
|
||||
konto = aufgabe.Bankkonto("Sarah", 100.50)
|
||||
self.assertEqual(konto.inhaber, "Sarah")
|
||||
self.assertEqual(konto.kontostand, 100.50)
|
||||
|
||||
def test_03_einzahlen_gueltig(self):
|
||||
"""Prüft erfolgreiches Einzahlen."""
|
||||
konto = aufgabe.Bankkonto("Tim", 50.0)
|
||||
erfolg = konto.einzahlen(25.0)
|
||||
self.assertTrue(erfolg, "Fehler: einzahlen(25.0) sollte True zurückgeben!")
|
||||
self.assertEqual(konto.kontostand, 75.0, "Fehler: Kontostand sollte nach 25€ Einzahlung auf 50€ bei 75.0€ liegen!")
|
||||
|
||||
def test_04_einzahlen_ungueltig(self):
|
||||
"""Prüft Einzahlen von negativen Beträgen oder 0."""
|
||||
konto = aufgabe.Bankkonto("Tim", 50.0)
|
||||
|
||||
erfolg_null = konto.einzahlen(0)
|
||||
self.assertFalse(erfolg_null, "Fehler: Einzahlen von 0 sollte False zurückgeben!")
|
||||
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand darf sich bei 0€ nicht ändern!")
|
||||
|
||||
erfolg_neg = konto.einzahlen(-10.0)
|
||||
self.assertFalse(erfolg_neg, "Fehler: Einzahlen von negativen Beträgen sollte False zurückgeben!")
|
||||
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand darf sich bei negativem Betrag nicht ändern!")
|
||||
|
||||
def test_05_auszahlen_erfolgreich(self):
|
||||
"""Prüft erfolgreiches Auszahlen."""
|
||||
konto = aufgabe.Bankkonto("Emma", 80.0)
|
||||
erfolg = konto.auszahlen(30.0)
|
||||
self.assertTrue(erfolg, "Fehler: auszahlen(30.0) bei 80€ Guthaben sollte True zurückgeben!")
|
||||
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand sollte nach Auszahlung 50.0€ sein!")
|
||||
|
||||
# Gesamtes Guthaben abheben
|
||||
erfolg_alles = konto.auszahlen(50.0)
|
||||
self.assertTrue(erfolg_alles, "Fehler: Gesamtes Guthaben abheben sollte möglich sein!")
|
||||
self.assertEqual(konto.kontostand, 0.0)
|
||||
|
||||
def test_06_auszahlen_zu_viel_oder_ungueltig(self):
|
||||
"""Prüft Auszahlen bei unzureichendem Guthaben oder negativem Betrag."""
|
||||
konto = aufgabe.Bankkonto("Emma", 20.0)
|
||||
|
||||
# Zu viel abheben
|
||||
erfolg = konto.auszahlen(50.0)
|
||||
self.assertFalse(erfolg, "Fehler: Auszahlen von mehr Geld als vorhanden ist muss False liefern!")
|
||||
self.assertEqual(konto.kontostand, 20.0, "Fehler: Kontostand darf sich nicht ändern, wenn Auszahlung fehlschlägt!")
|
||||
|
||||
# Negativer Betrag
|
||||
erfolg_neg = konto.auszahlen(-5.0)
|
||||
self.assertFalse(erfolg_neg, "Fehler: Auszahlen von negativen Beträgen muss False liefern!")
|
||||
self.assertEqual(konto.kontostand, 20.0)
|
||||
|
||||
def test_07_info_text(self):
|
||||
"""Prüft die Formatierung der info_text-Methode."""
|
||||
konto = aufgabe.Bankkonto("Mia", 25.5)
|
||||
text = konto.info_text()
|
||||
erwartet = "Konto von Mia: 25.50 Euro"
|
||||
self.assertEqual(
|
||||
text,
|
||||
erwartet,
|
||||
f"Fehler: Text weicht ab!\nErwartet: '{erwartet}'\nErhalten: '{text}'"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
69
03_methoden_und_verhalten/README.md
Normal file
69
03_methoden_und_verhalten/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Kapitel 03: Methoden & Verhalten – Wenn Objekte lebendig werden ⚔️
|
||||
|
||||
Bisher haben unsere Objekte hauptsächlich Daten gespeichert (wie Name oder Kontostand). Aber Objekte können noch viel mehr: **Sie können Aktionen ausführen!**
|
||||
|
||||
Funktionen, die innerhalb einer Klasse definiert sind, nennt man **Methoden**.
|
||||
|
||||
---
|
||||
|
||||
## 🏃♀️ Was unterscheidet eine Methode von einer normalen Funktion?
|
||||
|
||||
1. Eine Methode steht **eingerückt in der Klasse**.
|
||||
2. Das **erste Argument ist immer `self`**.
|
||||
3. Über `self` hat die Methode direkten Zugriff auf alle Eigenschaften des Objekts und kann diese verändern.
|
||||
|
||||
```python
|
||||
class Zauberer:
|
||||
def __init__(self, name, mana=50):
|
||||
self.name = name
|
||||
self.mana = mana
|
||||
|
||||
# Das hier ist eine Methode:
|
||||
def zauberspruch_wirken(self, mana_kosten):
|
||||
if self.mana >= mana_kosten:
|
||||
self.mana -= mana_kosten
|
||||
print(f"{self.name} wirkt einen Zauber! Verbleibendes Mana: {self.mana}")
|
||||
return True
|
||||
else:
|
||||
print(f"{self.name} hat nicht genug Mana!")
|
||||
return False
|
||||
|
||||
merlin = Zauberer("Merlin", 30)
|
||||
merlin.zauberspruch_wirken(10) # self wird automatisch merlin!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Wenn Objekte miteinander interagieren!
|
||||
|
||||
Das Spannendste an der objektorientierten Programmierung ist, wenn **ein Objekt mit einem anderen Objekt interagiert**.
|
||||
|
||||
Du kannst ein ganzes Objekt als Parameter an die Methode eines anderen Objekts übergeben:
|
||||
|
||||
```python
|
||||
class Spieler:
|
||||
def __init__(self, name, geld):
|
||||
self.name = name
|
||||
self.geld = geld
|
||||
|
||||
def bezahle_an(self, empfaenger, betrag):
|
||||
if self.geld >= betrag:
|
||||
self.geld -= betrag
|
||||
empfaenger.geld += betrag # Wir greifen direkt auf das andere Objekt zu!
|
||||
return True
|
||||
return False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe: Das RPG-Duell
|
||||
|
||||
In `aufgabe.py` erstellst du eine Klasse `Held` für ein Rollenspiel:
|
||||
- Ein Held hat `name`, `leben`, `max_leben` und `angriffskraft`.
|
||||
- Ein Held kann Schaden erleiden (`schaden_erleiden`), geheilt werden (`heilen`) und einen anderen Helden angreifen (`angreifen(gegner)`).
|
||||
- Du sorgst mit Bedingungen dafür, dass die Lebenspunkte nicht unter `0` fallen und nicht über `max_leben` steigen!
|
||||
|
||||
### Testen:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
101
03_methoden_und_verhalten/aufgabe.py
Normal file
101
03_methoden_und_verhalten/aufgabe.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
Kapitel 03: Methoden & Verhalten – Helden-Duell
|
||||
==============================================
|
||||
|
||||
In dieser Aufgabe programmierst du eine Klasse 'Held' für ein Rollenspiel.
|
||||
"""
|
||||
|
||||
class Held:
|
||||
# ==========================================================================
|
||||
# TODO 1: Konstruktor __init__(self, name, leben=100, angriffskraft=15)
|
||||
# Speichere:
|
||||
# - self.name = name
|
||||
# - self.leben = leben
|
||||
# - self.max_leben = leben (das maximale Leben entspricht dem Startleben)
|
||||
# - self.angriffskraft = angriffskraft
|
||||
# - self.ist_am_leben = True
|
||||
# ==========================================================================
|
||||
def __init__(self, name, leben=100, angriffskraft=15):
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 2: Methode "schaden_erleiden(self, schaden)"
|
||||
# Regeln:
|
||||
# 1. Wenn der Held bereits besiegt ist (not self.ist_am_leben) oder schaden <= 0:
|
||||
# - Es passiert nichts, gib 0 zurück.
|
||||
# 2. Ziehe 'schaden' von 'self.leben' ab.
|
||||
# 3. Wenn 'self.leben' <= 0 wird:
|
||||
# - Setze 'self.leben' genau auf 0.
|
||||
# - Setze 'self.ist_am_leben' auf False.
|
||||
# 4. Gib den erlittenen Schaden (int/float) zurück.
|
||||
# ==========================================================================
|
||||
def schaden_erleiden(self, schaden):
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 3: Methode "heilen(self, heilung)"
|
||||
# Regeln:
|
||||
# 1. Wenn der Held besiegt ist (not self.ist_am_leben) oder heilung <= 0:
|
||||
# - Tote Helden können nicht geheilt werden! Gib 0 zurück.
|
||||
# 2. Erhöhe das Leben um 'heilung', aber ACHTUNG:
|
||||
# - 'self.leben' darf niemals größer als 'self.max_leben' werden!
|
||||
# - Beispiel: Wenn leben=90, max_leben=100 und heilung=20:
|
||||
# Das Leben wird 100 (nicht 110) und die tatsächliche Heilung war 10.
|
||||
# 3. Gib die tatsächlich geheilte Menge an Lebenspunkten zurück.
|
||||
# ==========================================================================
|
||||
def heilen(self, heilung):
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 4: Methode "angreifen(self, gegner)"
|
||||
# Parameter 'gegner' ist ein anderes Held-Objekt!
|
||||
# Regeln:
|
||||
# 1. Prüfe, ob BEIDE Helden noch am Leben sind (self.ist_am_leben und gegner.ist_am_leben).
|
||||
# 2. Wenn ja:
|
||||
# - Rufe auf dem 'gegner' die Methode 'schaden_erleiden' auf mit self.angriffskraft als Wert.
|
||||
# - Gib True zurück.
|
||||
# 3. Wenn einer von beiden (oder beide) nicht mehr am Leben ist:
|
||||
# - Greife nicht an und gib False zurück.
|
||||
# ==========================================================================
|
||||
def angreifen(self, gegner):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 5: Methode "status_text(self)"
|
||||
# Gibt einen formatierten String zurück:
|
||||
# Wenn am Leben:
|
||||
# "[{self.name}] HP: {self.leben}/{self.max_leben} | Kraft: {self.angriffskraft} | Status: Lebendig"
|
||||
# Wenn besiegt:
|
||||
# "[{self.name}] HP: 0/{self.max_leben} | Kraft: {self.angriffskraft} | Status: Besiegt"
|
||||
# ==========================================================================
|
||||
def status_text(self):
|
||||
# Schreibe hier deinen Code für TODO 5:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Kampfsimulator zum Ausprobieren:
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
print("⚔️ --- ARENA DER HELDEN --- ⚔️")
|
||||
ritter = Held("Arthur", leben=80, angriffskraft=20)
|
||||
drache = Held("Feuerspei", leben=120, angriffskraft=25)
|
||||
|
||||
print(ritter.status_text())
|
||||
print(drache.status_text())
|
||||
|
||||
print("\n--- Runde 1 ---")
|
||||
ritter.angreifen(drache)
|
||||
print(drache.status_text())
|
||||
|
||||
drache.angreifen(ritter)
|
||||
print(ritter.status_text())
|
||||
|
||||
print("\n--- Arthur trinkt einen Heiltrank ---")
|
||||
geheilt = ritter.heilen(15)
|
||||
print(f"Arthur heilt sich um {geheilt} HP!")
|
||||
print(ritter.status_text())
|
||||
51
03_methoden_und_verhalten/musterloesung.py
Normal file
51
03_methoden_und_verhalten/musterloesung.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
Kapitel 03: Methoden & Verhalten – Helden-Duell – Musterlösung
|
||||
=============================================================
|
||||
"""
|
||||
|
||||
class Held:
|
||||
def __init__(self, name, leben=100, angriffskraft=15):
|
||||
self.name = name
|
||||
self.leben = leben
|
||||
self.max_leben = leben
|
||||
self.angriffskraft = angriffskraft
|
||||
self.ist_am_leben = True
|
||||
|
||||
def schaden_erleiden(self, schaden):
|
||||
if not self.ist_am_leben or schaden <= 0:
|
||||
return 0
|
||||
|
||||
tatsaechlicher_schaden = min(schaden, self.leben)
|
||||
self.leben -= schaden
|
||||
if self.leben <= 0:
|
||||
self.leben = 0
|
||||
self.ist_am_leben = False
|
||||
return tatsaechlicher_schaden
|
||||
|
||||
def heilen(self, heilung):
|
||||
if not self.ist_am_leben or heilung <= 0:
|
||||
return 0
|
||||
|
||||
alter_wert = self.leben
|
||||
self.leben = min(self.max_leben, self.leben + heilung)
|
||||
tatsaechlich_geheilt = self.leben - alter_wert
|
||||
return tatsaechlich_geheilt
|
||||
|
||||
def angreifen(self, gegner):
|
||||
if self.ist_am_leben and gegner.ist_am_leben:
|
||||
gegner.schaden_erleiden(self.angriffskraft)
|
||||
return True
|
||||
return False
|
||||
|
||||
def status_text(self):
|
||||
status_str = "Lebendig" if self.ist_am_leben else "Besiegt"
|
||||
return f"[{self.name}] HP: {self.leben}/{self.max_leben} | Kraft: {self.angriffskraft} | Status: {status_str}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ritter = Held("Arthur", 80, 20)
|
||||
drache = Held("Feuerspei", 120, 25)
|
||||
print(ritter.status_text())
|
||||
print(drache.status_text())
|
||||
ritter.angreifen(drache)
|
||||
print(drache.status_text())
|
||||
101
03_methoden_und_verhalten/test_aufgabe.py
Normal file
101
03_methoden_und_verhalten/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel03(unittest.TestCase):
|
||||
|
||||
def test_01_init(self):
|
||||
"""Prüft die Initialisierung des Helden."""
|
||||
h = aufgabe.Held("Galahad", 90, 18)
|
||||
self.assertEqual(h.name, "Galahad")
|
||||
self.assertEqual(h.leben, 90)
|
||||
self.assertEqual(h.max_leben, 90)
|
||||
self.assertEqual(h.angriffskraft, 18)
|
||||
self.assertTrue(h.ist_am_leben)
|
||||
|
||||
def test_02_schaden_erleiden(self):
|
||||
"""Prüft Schadensberechnung und K.O.-Logik."""
|
||||
h = aufgabe.Held("Krieger", 50, 10)
|
||||
|
||||
# Normaler Schaden
|
||||
schaden = h.schaden_erleiden(20)
|
||||
self.assertEqual(schaden, 20)
|
||||
self.assertEqual(h.leben, 30)
|
||||
self.assertTrue(h.ist_am_leben)
|
||||
|
||||
# Schaden, der das Leben auf 0 oder darunter bringt
|
||||
h.schaden_erleiden(40)
|
||||
self.assertEqual(h.leben, 0, "Leben darf nach Über-Schaden nicht negativ werden, sondern muss 0 sein!")
|
||||
self.assertFalse(h.ist_am_leben, "Held muss nach 0 HP als besiegt (ist_am_leben=False) markiert werden!")
|
||||
|
||||
# Schaden an bereits besiegtem Helden
|
||||
schaden_tot = h.schaden_erleiden(10)
|
||||
self.assertEqual(schaden_tot, 0)
|
||||
self.assertEqual(h.leben, 0)
|
||||
|
||||
def test_03_heilen(self):
|
||||
"""Prüft die Heilungsfunktion und max_leben Begrenzung."""
|
||||
h = aufgabe.Held("Priester", 100, 5)
|
||||
h.leben = 70
|
||||
|
||||
# Teilweise Heilung
|
||||
geheilt = h.heilen(15)
|
||||
self.assertEqual(geheilt, 15)
|
||||
self.assertEqual(h.leben, 85)
|
||||
|
||||
# Heilung über max_leben hinaus
|
||||
geheilt_cap = h.heilen(30)
|
||||
self.assertEqual(geheilt_cap, 15, "Es sollten nur 15 HP geheilt werden, da max_leben=100!")
|
||||
self.assertEqual(h.leben, 100)
|
||||
|
||||
# Heilung eines toten Helden
|
||||
h.leben = 0
|
||||
h.ist_am_leben = False
|
||||
geheilt_tot = h.heilen(50)
|
||||
self.assertEqual(geheilt_tot, 0, "Ein besiegter Held kann nicht geheilt werden!")
|
||||
self.assertEqual(h.leben, 0)
|
||||
|
||||
def test_04_angreifen(self):
|
||||
"""Prüft die Interaktion beim Angriff."""
|
||||
held1 = aufgabe.Held("Held", 50, 20)
|
||||
held2 = aufgabe.Held("Monster", 30, 10)
|
||||
|
||||
erfolg = held1.angreifen(held2)
|
||||
self.assertTrue(erfolg)
|
||||
self.assertEqual(held2.leben, 10)
|
||||
self.assertTrue(held2.ist_am_leben)
|
||||
|
||||
# Zweiter Angriff besiegt Monster
|
||||
erfolg2 = held1.angreifen(held2)
|
||||
self.assertTrue(erfolg2)
|
||||
self.assertEqual(held2.leben, 0)
|
||||
self.assertFalse(held2.ist_am_leben)
|
||||
|
||||
# Weiterer Angriff auf besiegtes Monster schlägt fehl
|
||||
erfolg3 = held1.angreifen(held2)
|
||||
self.assertFalse(erfolg3, "Ein Angriff auf ein besiegtes Ziel sollte False zurückgeben!")
|
||||
|
||||
def test_05_status_text(self):
|
||||
"""Prüft die Statusanzeige."""
|
||||
h = aufgabe.Held("Arthur", 100, 20)
|
||||
self.assertEqual(
|
||||
h.status_text(),
|
||||
"[Arthur] HP: 100/100 | Kraft: 20 | Status: Lebendig"
|
||||
)
|
||||
h.schaden_erleiden(100)
|
||||
self.assertEqual(
|
||||
h.status_text(),
|
||||
"[Arthur] HP: 0/100 | Kraft: 20 | Status: Besiegt"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
77
04_str_und_darstellung/README.md
Normal file
77
04_str_und_darstellung/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Kapitel 04: Schöne Ausgaben mit `__str__` 🧾
|
||||
|
||||
Hast du schon mal versucht, ein selbst erstelltes Objekt direkt mit `print()` auszugeben?
|
||||
|
||||
```python
|
||||
class Buch:
|
||||
def __init__(self, titel, autor):
|
||||
self.titel = titel
|
||||
self.autor = autor
|
||||
|
||||
mein_buch = Buch("Harry Potter", "J.K. Rowling")
|
||||
print(mein_buch)
|
||||
```
|
||||
Die Ausgabe sieht dann so aus:
|
||||
```
|
||||
<__main__.Buch object at 0x7fa2b0485d90>
|
||||
```
|
||||
Das ist kryptisch und hilft im Alltag nicht weiter. Aber Python hat dafür eine geniale Dunder-Methode: **`__str__`**!
|
||||
|
||||
---
|
||||
|
||||
## ✨ Die `__str__`-Methode
|
||||
|
||||
Wann immer Python ein Objekt in einen Text umwandeln soll (z.B. bei `print(mein_objekt)` oder `str(mein_objekt)` oder in einem f-String `f"Das Buch: {mein_objekt}"`), schaut Python nach, ob deine Klasse eine `__str__`-Methode hat.
|
||||
|
||||
Wenn ja, ruft Python sie automatisch auf:
|
||||
|
||||
```python
|
||||
class Buch:
|
||||
def __init__(self, titel, autor):
|
||||
self.titel = titel
|
||||
self.autor = autor
|
||||
|
||||
def __str__(self):
|
||||
# Muss immer einen STRING zurückgeben (return)!
|
||||
return f"'{self.titel}' von {self.autor}"
|
||||
|
||||
mein_buch = Buch("Harry Potter", "J.K. Rowling")
|
||||
print(mein_buch) # Ausgabe: 'Harry Potter' von J.K. Rowling 🎉
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧺 Objekte in Listen verwalten
|
||||
|
||||
Oft hat man Objekte, die eine Liste von *anderen* Objekten verwalten (z.B. ein Warenkorb, der Artikel enthält, oder ein Bus, der Fahrgäste enthält):
|
||||
|
||||
```python
|
||||
class Schultasche:
|
||||
def __init__(self):
|
||||
self.buecher = [] # Startet mit einer leeren Liste
|
||||
|
||||
def buch_einpacken(self, buch):
|
||||
self.buecher.append(buch)
|
||||
|
||||
def inhalt_anzeigen(self):
|
||||
for b in self.buecher:
|
||||
print(f"- {b}") # Hier wird automatisch b.__str__() genutzt!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe: Der Supermarkt-Kassenzettel
|
||||
|
||||
In `aufgabe.py` programmierst du zwei Klassen:
|
||||
1. `Artikel`: Ein Produkt mit Name, Einzelpreis und Anzahl.
|
||||
- `__str__`: Erzeugt einen Kassenbon-Text wie `"3x Apfel (je 0.50 €) = 1.50 €"`.
|
||||
- `gesamtpreis()`: Berechnet den Preis für diesen Artikel (`preis * anzahl`).
|
||||
2. `Warenkorb`: Verwaltet eine Liste von `Artikel`-Objekten.
|
||||
- `artikel_hinzufuegen()`: Packt einen Artikel in den Warenkorb.
|
||||
- `gesamtsumme()`: Berechnet die Gesamtsumme aller Artikel.
|
||||
- `bon_text()`: Erzeugt einen formatierten mehrzeiligen Kassenbon.
|
||||
|
||||
### Testen:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
105
04_str_und_darstellung/aufgabe.py
Normal file
105
04_str_und_darstellung/aufgabe.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
Kapitel 04: __str__ und Objektlisten – Supermarkt & Warenkorb
|
||||
============================================================
|
||||
|
||||
In dieser Aufgabe erstellst du zwei Klassen: 'Artikel' und 'Warenkorb'.
|
||||
"""
|
||||
|
||||
class Artikel:
|
||||
# ==========================================================================
|
||||
# TODO 1: Konstruktor für Artikel
|
||||
# Parameter: self, name (str), preis (float), anzahl (int, Standardwert 1)
|
||||
# Speichere: self.name, self.preis, self.anzahl
|
||||
# ==========================================================================
|
||||
def __init__(self, name, preis, anzahl=1):
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 2: Methode "gesamtpreis(self)"
|
||||
# Gibt den Preis für diesen Artikel multipliziert mit der Anzahl zurück.
|
||||
# Beispiel: 3 Äpfel à 0.50€ -> 1.50
|
||||
# ==========================================================================
|
||||
def gesamtpreis(self):
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 3: Dunder-Methode "__str__(self)"
|
||||
# Gibt einen String im folgenden Format zurück:
|
||||
# "{anzahl}x {name} (je {preis:.2f} €) = {gesamtpreis:.2f} €"
|
||||
#
|
||||
# Beispiel:
|
||||
# Bei name="Milch", preis=1.29, anzahl=2:
|
||||
# -> "2x Milch (je 1.29 €) = 2.58 €"
|
||||
# ==========================================================================
|
||||
def __str__(self):
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
|
||||
class Warenkorb:
|
||||
# ==========================================================================
|
||||
# TODO 4: Konstruktor für Warenkorb
|
||||
# Parameter: self
|
||||
# Initialisiere: self.artikel_liste als leere Liste []
|
||||
# ==========================================================================
|
||||
def __init__(self):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 5: Methode "artikel_hinzufuegen(self, artikel)"
|
||||
# Fügt das übergebene Artikel-Objekt an 'self.artikel_liste' an (.append).
|
||||
# ==========================================================================
|
||||
def artikel_hinzufuegen(self, artikel):
|
||||
# Schreibe hier deinen Code für TODO 5:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 6: Methode "gesamtsumme(self)"
|
||||
# Berechnet die Summe der Gesamtpreise aller Artikel in self.artikel_liste
|
||||
# und gibt diese als float zurück. (Tipp: Schleife oder sum())
|
||||
# Bei leerem Warenkorb soll 0.0 zurückgegeben werden.
|
||||
# ==========================================================================
|
||||
def gesamtsumme(self):
|
||||
# Schreibe hier deinen Code für TODO 6:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 7: Methode "bon_text(self)"
|
||||
# Erzeugt einen formatierten mehrzeiligen String für den Kassenzettel.
|
||||
# Format:
|
||||
# --- KASSENZETTEL ---
|
||||
# 2x Milch (je 1.29 €) = 2.58 €
|
||||
# 1x Brot (je 2.49 €) = 2.49 €
|
||||
# --------------------
|
||||
# Gesamtsumme: 5.07 €
|
||||
#
|
||||
# Tipp: Baue eine Liste von Zeilen und verbinde sie mit "\n".join(...)
|
||||
# Die Zeilen für die Artikel erhältst du einfach mit str(artikel)!
|
||||
# ==========================================================================
|
||||
def bon_text(self):
|
||||
# Schreibe hier deinen Code für TODO 7:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Hauptprogramm zum Ausprobieren:
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
print("🛒 --- SUPERMARKT EINKAUF --- 🛒")
|
||||
korb = Warenkorb()
|
||||
|
||||
a1 = Artikel("Milch", 1.29, 2)
|
||||
a2 = Artikel("Brot", 2.49, 1)
|
||||
a3 = Artikel("Schokolade", 0.99, 3)
|
||||
|
||||
korb.artikel_hinzufuegen(a1)
|
||||
korb.artikel_hinzufuegen(a2)
|
||||
korb.artikel_hinzufuegen(a3)
|
||||
|
||||
if korb.artikel_liste:
|
||||
print(korb.bon_text())
|
||||
else:
|
||||
print("Hinweis: Implementiere die TODOs, um den Kassenbon zu drucken!")
|
||||
43
04_str_und_darstellung/musterloesung.py
Normal file
43
04_str_und_darstellung/musterloesung.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
Kapitel 04: __str__ und Objektlisten – Musterlösung
|
||||
===================================================
|
||||
"""
|
||||
|
||||
class Artikel:
|
||||
def __init__(self, name, preis, anzahl=1):
|
||||
self.name = name
|
||||
self.preis = float(preis)
|
||||
self.anzahl = int(anzahl)
|
||||
|
||||
def gesamtpreis(self):
|
||||
return self.preis * self.anzahl
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.anzahl}x {self.name} (je {self.preis:.2f} €) = {self.gesamtpreis():.2f} €"
|
||||
|
||||
|
||||
class Warenkorb:
|
||||
def __init__(self):
|
||||
self.artikel_liste = []
|
||||
|
||||
def artikel_hinzufuegen(self, artikel):
|
||||
self.artikel_liste.append(artikel)
|
||||
|
||||
def gesamtsumme(self):
|
||||
return sum(item.gesamtpreis() for item in self.artikel_liste)
|
||||
|
||||
def bon_text(self):
|
||||
zeilen = ["--- KASSENZETTEL ---"]
|
||||
for artikel in self.artikel_liste:
|
||||
zeilen.append(str(artikel))
|
||||
zeilen.append("--------------------")
|
||||
zeilen.append(f"Gesamtsumme: {self.gesamtsumme():.2f} €")
|
||||
return "\n".join(zeilen)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
korb = Warenkorb()
|
||||
korb.artikel_hinzufuegen(Artikel("Milch", 1.29, 2))
|
||||
korb.artikel_hinzufuegen(Artikel("Brot", 2.49, 1))
|
||||
korb.artikel_hinzufuegen(Artikel("Schokolade", 0.99, 3))
|
||||
print(korb.bon_text())
|
||||
68
04_str_und_darstellung/test_aufgabe.py
Normal file
68
04_str_und_darstellung/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel04(unittest.TestCase):
|
||||
|
||||
def test_01_artikel_init_und_gesamtpreis(self):
|
||||
"""Prüft Artikel-Konstruktor und gesamtpreis."""
|
||||
a = aufgabe.Artikel("Apfel", 0.60, 4)
|
||||
self.assertEqual(a.name, "Apfel")
|
||||
self.assertEqual(a.preis, 0.60)
|
||||
self.assertEqual(a.anzahl, 4)
|
||||
self.assertAlmostEqual(a.gesamtpreis(), 2.40, places=2)
|
||||
|
||||
def test_02_artikel_str(self):
|
||||
"""Prüft die __str__ Implementierung von Artikel."""
|
||||
a = aufgabe.Artikel("Milch", 1.29, 2)
|
||||
erwartet = "2x Milch (je 1.29 €) = 2.58 €"
|
||||
self.assertEqual(str(a), erwartet)
|
||||
|
||||
def test_03_warenkorb_init(self):
|
||||
"""Prüft, ob der Warenkorb mit einer leeren Liste startet."""
|
||||
korb = aufgabe.Warenkorb()
|
||||
self.assertTrue(hasattr(korb, "artikel_liste"))
|
||||
self.assertEqual(korb.artikel_liste, [])
|
||||
|
||||
def test_04_artikel_hinzufuegen_und_gesamtsumme(self):
|
||||
"""Prüft das Hinzufügen von Artikeln und die Gesamtsumme."""
|
||||
korb = aufgabe.Warenkorb()
|
||||
self.assertAlmostEqual(korb.gesamtsumme(), 0.0, places=2)
|
||||
|
||||
a1 = aufgabe.Artikel("Brot", 2.50, 2) # 5.00 €
|
||||
a2 = aufgabe.Artikel("Butter", 1.80, 1) # 1.80 €
|
||||
|
||||
korb.artikel_hinzufuegen(a1)
|
||||
korb.artikel_hinzufuegen(a2)
|
||||
|
||||
self.assertEqual(len(korb.artikel_liste), 2)
|
||||
self.assertAlmostEqual(korb.gesamtsumme(), 6.80, places=2)
|
||||
|
||||
def test_05_bon_text(self):
|
||||
"""Prüft den formatierten Bon-Text."""
|
||||
korb = aufgabe.Warenkorb()
|
||||
korb.artikel_hinzufuegen(aufgabe.Artikel("Kaffee", 4.50, 1))
|
||||
korb.artikel_hinzufuegen(aufgabe.Artikel("Keks", 1.20, 3))
|
||||
|
||||
bon = korb.bon_text()
|
||||
erwartete_zeilen = [
|
||||
"--- KASSENZETTEL ---",
|
||||
"1x Kaffee (je 4.50 €) = 4.50 €",
|
||||
"3x Keks (je 1.20 €) = 3.60 €",
|
||||
"--------------------",
|
||||
"Gesamtsumme: 8.10 €"
|
||||
]
|
||||
erwartet = "\n".join(erwartete_zeilen)
|
||||
self.assertEqual(bon.strip(), erwartet.strip())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
71
05_objekte_kombinieren/README.md
Normal file
71
05_objekte_kombinieren/README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Kapitel 05: Komposition – Objekte kombinieren & filtern 🎵
|
||||
|
||||
In der echten Softwareentwicklung (wie bei Spotify, Instagram oder Netflix) besteht ein System fast immer aus vielen verschiedenen Klassen, die zusammenarbeiten.
|
||||
|
||||
Wenn eine Klasse andere Objekte besitzt oder verwaltet, nennt man das **Komposition** (oder *"hat-ein"-Beziehung*):
|
||||
- Eine **Playlist** *hat viele* **Songs**.
|
||||
- Ein **Kino** *hat viele* **Säle** und **Filme**.
|
||||
- Eine **Schule** *hat viele* **Klassen** und **Schüler**.
|
||||
|
||||
---
|
||||
|
||||
## 🎶 Das Zusammenspiel: Song & Playlist
|
||||
|
||||
Schauen wir uns an, wie eine Playlist mit Songs arbeiten kann:
|
||||
|
||||
```python
|
||||
class Song:
|
||||
def __init__(self, titel, dauer_sekunden):
|
||||
self.titel = titel
|
||||
self.dauer_sekunden = dauer_sekunden
|
||||
|
||||
class Playlist:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.songs = [] # Hier landen die Song-Objekte
|
||||
|
||||
def hinzufuegen(self, song):
|
||||
self.songs.append(song)
|
||||
|
||||
def finde_lange_songs(self, min_sekunden):
|
||||
gefundene = []
|
||||
for song in self.songs:
|
||||
if song.dauer_sekunden >= min_sekunden:
|
||||
gefundene.append(song)
|
||||
return gefundene
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ Zeit-Formatierung: Aus Sekunden werden Minuten & Sekunden
|
||||
|
||||
Um z.B. `215` Sekunden als `"03:35"` darzustellen, teilt man mit Ganzzahldivision `//` und Modulo `%`:
|
||||
```python
|
||||
sekunden_gesamt = 215
|
||||
minuten = sekunden_gesamt // 60 # 3
|
||||
sekunden = sekunden_gesamt % 60 # 35
|
||||
|
||||
# Mit :02d wird immer zweistellig mit führender Null formatiert:
|
||||
print(f"{minuten:02d}:{sekunden:02d}") # "03:35"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe: Der Spotify-Playlist-Manager
|
||||
|
||||
In `aufgabe.py` baust du deine eigene Musikverwaltung:
|
||||
1. **Klasse `Song`**:
|
||||
- Attribute: `titel`, `kuenstler`, `dauer_sekunden`
|
||||
- Methode: `formatierte_dauer()` -> `"MM:SS"`
|
||||
- `__str__()` -> `"{titel}" von {kuenstler} ({MM:SS})`
|
||||
2. **Klasse `Playlist`**:
|
||||
- `song_hinzufuegen(song)`
|
||||
- `anzahl_songs()`
|
||||
- `gesamtdauer_sekunden()`
|
||||
- `finde_songs_von(kuenstler)` (Groß-/Kleinschreibung ignorieren!)
|
||||
- `laengster_song()` (liefert das Song-Objekt mit der längsten Dauer)
|
||||
|
||||
### Testen:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
123
05_objekte_kombinieren/aufgabe.py
Normal file
123
05_objekte_kombinieren/aufgabe.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
Kapitel 05: Komposition – Song & Playlist Manager
|
||||
=================================================
|
||||
|
||||
In dieser Aufgabe baust du eine Musikverwaltung mit zwei Klassen:
|
||||
'Song' und 'Playlist'.
|
||||
"""
|
||||
|
||||
class Song:
|
||||
# ==========================================================================
|
||||
# TODO 1: Konstruktor für Song
|
||||
# Parameter: self, titel (str), kuenstler (str), dauer_sekunden (int)
|
||||
# Speichere: self.titel, self.kuenstler, self.dauer_sekunden
|
||||
# ==========================================================================
|
||||
def __init__(self, titel, kuenstler, dauer_sekunden):
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 2: Methode "formatierte_dauer(self)"
|
||||
# Rechnet 'self.dauer_sekunden' in Minuten und Sekunden um und gibt einen
|
||||
# String im Format "MM:SS" zurück (z.B. 215 -> "03:35", 70 -> "01:10").
|
||||
# Tipp: Verwende // 60 und % 60 sowie f"{minuten:02d}:{sekunden:02d}"
|
||||
# ==========================================================================
|
||||
def formatierte_dauer(self):
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 3: Dunder-Methode "__str__(self)"
|
||||
# Gibt einen String im folgenden Format zurück:
|
||||
# '"{titel}" von {kuenstler} ({dauer})'
|
||||
#
|
||||
# Beispiel: '"Flowers" von Miley Cyrus (03:20)'
|
||||
# ==========================================================================
|
||||
def __str__(self):
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
|
||||
class Playlist:
|
||||
# ==========================================================================
|
||||
# TODO 4: Konstruktor für Playlist
|
||||
# Parameter: self, name (str)
|
||||
# Speichere: self.name, self.songs (als leere Liste [])
|
||||
# ==========================================================================
|
||||
def __init__(self, name):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 5: Methode "song_hinzufuegen(self, song)"
|
||||
# Hängt das übergebene Song-Objekt an 'self.songs' an.
|
||||
# ==========================================================================
|
||||
def song_hinzufuegen(self, song):
|
||||
# Schreibe hier deinen Code für TODO 5:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 6: Methode "anzahl_songs(self)"
|
||||
# Gibt die Anzahl der Songs in der Playlist als Integer zurück.
|
||||
# ==========================================================================
|
||||
def anzahl_songs(self):
|
||||
# Schreibe hier deinen Code für TODO 6:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 7: Methode "gesamtdauer_sekunden(self)"
|
||||
# Gibt die Summe der Sekunden aller Songs in der Playlist zurück.
|
||||
# Bei leerer Playlist: 0
|
||||
# ==========================================================================
|
||||
def gesamtdauer_sekunden(self):
|
||||
# Schreibe hier deinen Code für TODO 7:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 8: Methode "finde_songs_von(self, kuenstler)"
|
||||
# Sucht alle Songs in self.songs, bei denen der Künstler übereinstimmt.
|
||||
# Wichtig: Groß- und Kleinschreibung soll ignoriert werden! (z.B. .lower())
|
||||
# Gibt eine Liste von passenden Song-Objekten zurück.
|
||||
# ==========================================================================
|
||||
def finde_songs_von(self, kuenstler):
|
||||
# Schreibe hier deinen Code für TODO 8:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 9: Methode "laengster_song(self)"
|
||||
# Findet den Song mit der längsten Spieldauer in self.songs.
|
||||
# - Gibt das gefundene Song-Objekt zurück.
|
||||
# - Gibt None zurück, wenn die Playlist leer ist.
|
||||
# ==========================================================================
|
||||
def laengster_song(self):
|
||||
# Schreibe hier deinen Code für TODO 9:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Hauptprogramm zum Ausprobieren:
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
print("🎧 --- MEINE PLAYLIST --- 🎧")
|
||||
meine_hits = Playlist("Sommer Vibes")
|
||||
|
||||
s1 = Song("Flowers", "Miley Cyrus", 200)
|
||||
s2 = Song("Cruel Summer", "Taylor Swift", 178)
|
||||
s3 = Song("Anti-Hero", "Taylor Swift", 201)
|
||||
s4 = Song("As It Was", "Harry Styles", 167)
|
||||
|
||||
meine_hits.song_hinzufuegen(s1)
|
||||
meine_hits.song_hinzufuegen(s2)
|
||||
meine_hits.song_hinzufuegen(s3)
|
||||
meine_hits.song_hinzufuegen(s4)
|
||||
|
||||
print(f"Playlist: {meine_hits.name} ({meine_hits.anzahl_songs()} Songs)")
|
||||
print(f"Gesamtlaufzeit: {meine_hits.gesamtdauer_sekunden()} Sekunden")
|
||||
|
||||
laengster = meine_hits.laengster_song()
|
||||
if laengster:
|
||||
print(f"Längster Song: {laengster}")
|
||||
|
||||
print("\nSongs von Taylor Swift:")
|
||||
for s in meine_hits.finde_songs_von("taylor swift"):
|
||||
print(f"- {s}")
|
||||
52
05_objekte_kombinieren/musterloesung.py
Normal file
52
05_objekte_kombinieren/musterloesung.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
Kapitel 05: Komposition – Song & Playlist Manager – Musterlösung
|
||||
================================================================
|
||||
"""
|
||||
|
||||
class Song:
|
||||
def __init__(self, titel, kuenstler, dauer_sekunden):
|
||||
self.titel = titel
|
||||
self.kuenstler = kuenstler
|
||||
self.dauer_sekunden = int(dauer_sekunden)
|
||||
|
||||
def formatierte_dauer(self):
|
||||
minuten = self.dauer_sekunden // 60
|
||||
sekunden = self.dauer_sekunden % 60
|
||||
return f"{minuten:02d}:{sekunden:02d}"
|
||||
|
||||
def __str__(self):
|
||||
return f'"{self.titel}" von {self.kuenstler} ({self.formatierte_dauer()})'
|
||||
|
||||
|
||||
class Playlist:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.songs = []
|
||||
|
||||
def song_hinzufuegen(self, song):
|
||||
self.songs.append(song)
|
||||
|
||||
def anzahl_songs(self):
|
||||
return len(self.songs)
|
||||
|
||||
def gesamtdauer_sekunden(self):
|
||||
return sum(s.dauer_sekunden for s in self.songs)
|
||||
|
||||
def finde_songs_von(self, kuenstler):
|
||||
kuenstler_lower = kuenstler.lower()
|
||||
return [s for s in self.songs if s.kuenstler.lower() == kuenstler_lower]
|
||||
|
||||
def laengster_song(self):
|
||||
if not self.songs:
|
||||
return None
|
||||
return max(self.songs, key=lambda s: s.dauer_sekunden)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
meine_hits = Playlist("Sommer Vibes")
|
||||
meine_hits.song_hinzufuegen(Song("Flowers", "Miley Cyrus", 200))
|
||||
meine_hits.song_hinzufuegen(Song("Cruel Summer", "Taylor Swift", 178))
|
||||
meine_hits.song_hinzufuegen(Song("Anti-Hero", "Taylor Swift", 201))
|
||||
|
||||
print(f"Playlist: {meine_hits.name} ({meine_hits.anzahl_songs()} Songs)")
|
||||
print(f"Längster Song: {meine_hits.laengster_song()}")
|
||||
87
05_objekte_kombinieren/test_aufgabe.py
Normal file
87
05_objekte_kombinieren/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel05(unittest.TestCase):
|
||||
|
||||
def test_01_song_init_und_dauer(self):
|
||||
"""Prüft Song Attribute und Formatierung der Dauer."""
|
||||
s = aufgabe.Song("Vampire", "Olivia Rodrigo", 219)
|
||||
self.assertEqual(s.titel, "Vampire")
|
||||
self.assertEqual(s.kuenstler, "Olivia Rodrigo")
|
||||
self.assertEqual(s.dauer_sekunden, 219)
|
||||
self.assertEqual(s.formatierte_dauer(), "03:39")
|
||||
|
||||
s_kurz = aufgabe.Song("Intro", "Kuenstler", 5)
|
||||
self.assertEqual(s_kurz.formatierte_dauer(), "00:05")
|
||||
|
||||
def test_02_song_str(self):
|
||||
"""Prüft die __str__ Methode von Song."""
|
||||
s = aufgabe.Song("Espresso", "Sabrina Carpenter", 175)
|
||||
erwartet = '"Espresso" von Sabrina Carpenter (02:55)'
|
||||
self.assertEqual(str(s), erwartet)
|
||||
|
||||
def test_03_playlist_hinzufuegen_und_dauer(self):
|
||||
"""Prüft Playlist Erstellung, Hinzufügen, Anzahl und Gesamtlaufzeit."""
|
||||
p = aufgabe.Playlist("Party")
|
||||
self.assertEqual(p.anzahl_songs(), 0)
|
||||
self.assertEqual(p.gesamtdauer_sekunden(), 0)
|
||||
self.assertIsNone(p.laengster_song())
|
||||
|
||||
s1 = aufgabe.Song("Song A", "Artist 1", 100)
|
||||
s2 = aufgabe.Song("Song B", "Artist 2", 150)
|
||||
p.song_hinzufuegen(s1)
|
||||
p.song_hinzufuegen(s2)
|
||||
|
||||
self.assertEqual(p.anzahl_songs(), 2)
|
||||
self.assertEqual(p.gesamtdauer_sekunden(), 250)
|
||||
|
||||
def test_04_finde_songs_von(self):
|
||||
"""Prüft die Filtersuche nach Künstlern (auch case-insensitive)."""
|
||||
p = aufgabe.Playlist("Mix")
|
||||
s1 = aufgabe.Song("Anti-Hero", "Taylor Swift", 200)
|
||||
s2 = aufgabe.Song("Cruel Summer", "Taylor Swift", 180)
|
||||
s3 = aufgabe.Song("Flowers", "Miley Cyrus", 210)
|
||||
|
||||
p.song_hinzufuegen(s1)
|
||||
p.song_hinzufuegen(s2)
|
||||
p.song_hinzufuegen(s3)
|
||||
|
||||
# Exakte Schreibweise
|
||||
ergebnis = p.finde_songs_von("Taylor Swift")
|
||||
self.assertEqual(len(ergebnis), 2)
|
||||
self.assertIn(s1, ergebnis)
|
||||
self.assertIn(s2, ergebnis)
|
||||
|
||||
# Kleinschreibung testen
|
||||
ergebnis_lower = p.finde_songs_von("taylor swift")
|
||||
self.assertEqual(len(ergebnis_lower), 2)
|
||||
|
||||
# Nicht vorhandener Künstler
|
||||
ergebnis_leer = p.finde_songs_von("Ed Sheeran")
|
||||
self.assertEqual(len(ergebnis_leer), 0)
|
||||
|
||||
def test_05_laengster_song(self):
|
||||
"""Prüft das Ermitteln des längsten Songs."""
|
||||
p = aufgabe.Playlist("Mix")
|
||||
s1 = aufgabe.Song("Kurz", "A", 120)
|
||||
s2 = aufgabe.Song("Lang", "B", 300)
|
||||
s3 = aufgabe.Song("Mittel", "C", 200)
|
||||
|
||||
p.song_hinzufuegen(s1)
|
||||
p.song_hinzufuegen(s2)
|
||||
p.song_hinzufuegen(s3)
|
||||
|
||||
self.assertIs(p.laengster_song(), s2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
48
06_abschlussprojekt_tamagotchi/README.md
Normal file
48
06_abschlussprojekt_tamagotchi/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Kapitel 06: Abschlussprojekt – Dein eigenes Tamagotchi! 🥚🐣
|
||||
|
||||
Herzlichen Glückwunsch! Du hast alle grundlegenden Konzepte der objektorientierten Programmierung gemeistert:
|
||||
- Klassen & Instanzen
|
||||
- `__init__` & `self`
|
||||
- Methoden & Zustandsveränderungen
|
||||
- `__str__` für ansprechende Textausgaben
|
||||
- Beziehungen zwischen Objekten
|
||||
|
||||
Jetzt setzen wir alles in einem **vollwertigen, interaktiven Mini-Projekt** zusammen: Einem virtuellen Haustier (Tamagotchi)!
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Wie funktioniert das Tamagotchi?
|
||||
|
||||
Dein Tamagotchi hat Eigenschaften, die sich durch deine Aktionen (und mit der Zeit) verändern:
|
||||
- **Hunger** (0 = vollgefressen, 100 = verhungert)
|
||||
- **Müdigkeit** (0 = voller Energie, 100 = total erschöpft)
|
||||
- **Glück** (0 = unglücklich, 100 = überglücklich)
|
||||
- **Alter in Tagen**
|
||||
|
||||
Du kannst mit ihm interagieren:
|
||||
- 🍎 **Füttern** (`fuettern`): Senkt den Hunger, macht etwas müde und etwas glücklicher.
|
||||
- ⚽ **Spielen** (`spielen`): Steigert das Glück, macht aber hungrig und müde. Zu müde Haustiere weigern sich zu spielen!
|
||||
- 💤 **Schlafen** (`schlafen`): Setzt die Müdigkeit auf 0, lässt einen Tag vergehen (`alter_tage += 1`), macht aber etwas hungrig.
|
||||
- ⏳ **Zeit vergeht** (`zeit_vergeht`): Das Haustier wird hungriger, müder und etwas trauriger. Wenn der Hunger 100 erreicht, schläft es für immer ein...
|
||||
|
||||
---
|
||||
|
||||
## 🕹️ Das interaktive Spiel
|
||||
|
||||
Sobald du die `Tamagotchi`-Klasse in `aufgabe.py` fertig programmiert hast und alle Tests grün sind, kannst du das Spiel direkt im Terminal starten:
|
||||
|
||||
```bash
|
||||
python3 tamagotchi_spiel.py
|
||||
```
|
||||
Dort kannst du deinem Haustier einen Namen geben und dich live um es kümmern!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgabe
|
||||
|
||||
Öffne `aufgabe.py` und implementiere die `Tamagotchi`-Klasse von TODO 1 bis TODO 6.
|
||||
|
||||
### Tests ausführen:
|
||||
```bash
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
129
06_abschlussprojekt_tamagotchi/aufgabe.py
Normal file
129
06_abschlussprojekt_tamagotchi/aufgabe.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"""
|
||||
Kapitel 06: Abschlussprojekt – Tamagotchi (Virtuelles Haustier)
|
||||
==============================================================
|
||||
|
||||
Programmiere hier die Klasse 'Tamagotchi'.
|
||||
"""
|
||||
|
||||
class Tamagotchi:
|
||||
# ==========================================================================
|
||||
# TODO 1: Konstruktor __init__(self, name, tierart="Drache")
|
||||
#
|
||||
# Initialisiere die folgenden Attribute:
|
||||
# - self.name = name
|
||||
# - self.tierart = tierart
|
||||
# - self.hunger = 50 (0 = pappsatt, 100 = verhungert)
|
||||
# - self.muedigkeit = 20 (0 = fit, 100 = erschöpft)
|
||||
# - self.glueck = 80 (0 = traurig, 100 = super glücklich)
|
||||
# - self.alter_tage = 0
|
||||
# - self.ist_lebendig = True
|
||||
# ==========================================================================
|
||||
def __init__(self, name, tierart="Drache"):
|
||||
# Schreibe hier deinen Code für TODO 1:
|
||||
pass
|
||||
|
||||
# Hilfsmethode (optional, aber sehr nützlich):
|
||||
# Hält einen Wert zwischen minimal 0 und maximal 100.
|
||||
def _begrenze(self, wert):
|
||||
return max(0, min(100, wert))
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 2: Methode "fuettern(self, menge=20)"
|
||||
#
|
||||
# Regeln:
|
||||
# 1. Wenn not self.ist_lebendig:
|
||||
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
|
||||
# 2. Wenn lebendig:
|
||||
# - Verringere 'self.hunger' um 'menge' (darf nicht unter 0 fallen).
|
||||
# - Erhöhe 'self.glueck' um 5 (darf nicht über 100 steigen).
|
||||
# - Erhöhe 'self.muedigkeit' um 5 (darf nicht über 100 steigen).
|
||||
# - Gib zurück: f"{self.name} mampft genüsslich! (Hunger: {self.hunger}/100)"
|
||||
# ==========================================================================
|
||||
def fuettern(self, menge=20):
|
||||
# Schreibe hier deinen Code für TODO 2:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 3: Methode "spielen(self, spass=25)"
|
||||
#
|
||||
# Regeln:
|
||||
# 1. Wenn not self.ist_lebendig:
|
||||
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
|
||||
# 2. Wenn 'self.muedigkeit' > 80 ist:
|
||||
# - Zu müde zum Spielen! Ändere keine Werte und gib zurück:
|
||||
# f"{self.name} ist zu müde zum Spielen und gähnt nur..."
|
||||
# 3. Wenn fit genug:
|
||||
# - Erhöhe 'self.glueck' um 'spass' (max 100).
|
||||
# - Erhöhe 'self.hunger' um 15 (max 100).
|
||||
# - Erhöhe 'self.muedigkeit' um 20 (max 100).
|
||||
# - Gib zurück: f"{self.name} hatte riesigen Spaß beim Spielen! (Glück: {self.glueck}/100)"
|
||||
# ==========================================================================
|
||||
def spielen(self, spass=25):
|
||||
# Schreibe hier deinen Code für TODO 3:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 4: Methode "schlafen(self)"
|
||||
#
|
||||
# Regeln:
|
||||
# 1. Wenn not self.ist_lebendig:
|
||||
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
|
||||
# 2. Wenn lebendig:
|
||||
# - Setze 'self.muedigkeit' auf 0.
|
||||
# - Erhöhe 'self.hunger' um 15 (max 100).
|
||||
# - Erhöhe 'self.alter_tage' um 1.
|
||||
# - Gib zurück: f"{self.name} hat tief geschlafen und ist ausgeruht! Ein neuer Tag bricht an."
|
||||
# ==========================================================================
|
||||
def schlafen(self):
|
||||
# Schreibe hier deinen Code für TODO 4:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 5: Methode "zeit_vergeht(self)"
|
||||
# Simuliert das Verstreichen von Zeit.
|
||||
#
|
||||
# Regeln:
|
||||
# 1. Wenn not self.ist_lebendig:
|
||||
# - Mach nichts, gib False zurück.
|
||||
# 2. Wenn lebendig:
|
||||
# - Erhöhe 'self.hunger' um 10 (max 100).
|
||||
# - Erhöhe 'self.muedigkeit' um 10 (max 100).
|
||||
# - Verringere 'self.glueck' um 10 (min 0).
|
||||
# - Prüfe, ob das Haustier stirbt:
|
||||
# Wenn self.hunger >= 100 oder self.glueck <= 0:
|
||||
# self.ist_lebendig = False
|
||||
# - Gib self.ist_lebendig zurück (True wenn noch am Leben, False wenn gestorben).
|
||||
# ==========================================================================
|
||||
def zeit_vergeht(self):
|
||||
# Schreibe hier deinen Code für TODO 5:
|
||||
pass
|
||||
|
||||
# ==========================================================================
|
||||
# TODO 6: Dunder-Methode "__str__(self)"
|
||||
# Gibt eine hübsche Statusübersicht als mehrzeiligen String zurück.
|
||||
#
|
||||
# Format (wenn lebendig):
|
||||
# "=== {self.name} ({self.tierart}, {self.alter_tage} Tage alt) ===\n"
|
||||
# "Hunger: {self.hunger}/100\n"
|
||||
# "Müdigkeit: {self.muedigkeit}/100\n"
|
||||
# "Glück: {self.glueck}/100"
|
||||
#
|
||||
# Format (wenn tot / not self.ist_lebendig):
|
||||
# "=== {self.name} ({self.tierart}) - RIP 🪦 ==="
|
||||
# ==========================================================================
|
||||
def __str__(self):
|
||||
# Schreibe hier deinen Code für TODO 6:
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Schneller Testlauf:
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
pet = Tamagotchi("Yoshi", "Dino")
|
||||
print(pet)
|
||||
print()
|
||||
print(pet.fuettern())
|
||||
print(pet.spielen())
|
||||
print(pet.schlafen())
|
||||
print(pet)
|
||||
81
06_abschlussprojekt_tamagotchi/musterloesung.py
Normal file
81
06_abschlussprojekt_tamagotchi/musterloesung.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""
|
||||
Kapitel 06: Abschlussprojekt – Tamagotchi – Musterlösung
|
||||
========================================================
|
||||
"""
|
||||
|
||||
class Tamagotchi:
|
||||
def __init__(self, name, tierart="Drache"):
|
||||
self.name = name
|
||||
self.tierart = tierart
|
||||
self.hunger = 50
|
||||
self.muedigkeit = 20
|
||||
self.glueck = 80
|
||||
self.alter_tage = 0
|
||||
self.ist_lebendig = True
|
||||
|
||||
def _begrenze(self, wert):
|
||||
return max(0, min(100, wert))
|
||||
|
||||
def fuettern(self, menge=20):
|
||||
if not self.ist_lebendig:
|
||||
return f"{self.name} reagiert nicht mehr..."
|
||||
|
||||
self.hunger = self._begrenze(self.hunger - menge)
|
||||
self.glueck = self._begrenze(self.glueck + 5)
|
||||
self.muedigkeit = self._begrenze(self.muedigkeit + 5)
|
||||
return f"{self.name} mampft genüsslich! (Hunger: {self.hunger}/100)"
|
||||
|
||||
def spielen(self, spass=25):
|
||||
if not self.ist_lebendig:
|
||||
return f"{self.name} reagiert nicht mehr..."
|
||||
|
||||
if self.muedigkeit > 80:
|
||||
return f"{self.name} ist zu müde zum Spielen und gähnt nur..."
|
||||
|
||||
self.glueck = self._begrenze(self.glueck + spass)
|
||||
self.hunger = self._begrenze(self.hunger + 15)
|
||||
self.muedigkeit = self._begrenze(self.muedigkeit + 20)
|
||||
return f"{self.name} hatte riesigen Spaß beim Spielen! (Glück: {self.glueck}/100)"
|
||||
|
||||
def schlafen(self):
|
||||
if not self.ist_lebendig:
|
||||
return f"{self.name} reagiert nicht mehr..."
|
||||
|
||||
self.muedigkeit = 0
|
||||
self.hunger = self._begrenze(self.hunger + 15)
|
||||
self.alter_tage += 1
|
||||
return f"{self.name} hat tief geschlafen und ist ausgeruht! Ein neuer Tag bricht an."
|
||||
|
||||
def zeit_vergeht(self):
|
||||
if not self.ist_lebendig:
|
||||
return False
|
||||
|
||||
self.hunger = self._begrenze(self.hunger + 10)
|
||||
self.muedigkeit = self._begrenze(self.muedigkeit + 10)
|
||||
self.glueck = self._begrenze(self.glueck - 10)
|
||||
|
||||
if self.hunger >= 100 or self.glueck <= 0:
|
||||
self.ist_lebendig = False
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
if not self.ist_lebendig:
|
||||
return f"=== {self.name} ({self.tierart}) - RIP 🪦 ==="
|
||||
|
||||
zeilen = [
|
||||
f"=== {self.name} ({self.tierart}, {self.alter_tage} Tage alt) ===",
|
||||
f"Hunger: {self.hunger}/100",
|
||||
f"Müdigkeit: {self.muedigkeit}/100",
|
||||
f"Glück: {self.glueck}/100"
|
||||
]
|
||||
return "\n".join(zeilen)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pet = Tamagotchi("Yoshi", "Dino")
|
||||
print(pet)
|
||||
print(pet.fuettern())
|
||||
print(pet.spielen())
|
||||
print(pet.schlafen())
|
||||
print(pet)
|
||||
111
06_abschlussprojekt_tamagotchi/tamagotchi_spiel.py
Normal file
111
06_abschlussprojekt_tamagotchi/tamagotchi_spiel.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
🎮 TAMAGOTCHI TERMINAL SPIEL 🎮
|
||||
===============================
|
||||
Führe dieses Skript aus, um dein Tamagotchi live im Terminal zu spielen:
|
||||
python3 tamagotchi_spiel.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
from aufgabe import Tamagotchi
|
||||
except ImportError:
|
||||
from musterloesung import Tamagotchi
|
||||
|
||||
|
||||
def zeichne_balken(wert, max_wert=100, laenge=15, umgekehrt=False):
|
||||
"""Erzeugt einen visuellen Ladebalken."""
|
||||
gefuellt = int((wert / max_wert) * laenge)
|
||||
ungefuellt = laenge - gefuellt
|
||||
|
||||
if umgekehrt:
|
||||
# Bei Hunger/Müdigkeit ist weniger besser (grün -> rot)
|
||||
farbe = "🟢" if wert < 40 else ("🟡" if wert < 75 else "🔴")
|
||||
else:
|
||||
# Bei Glück ist mehr besser
|
||||
farbe = "🟢" if wert > 60 else ("🟡" if wert > 30 else "🔴")
|
||||
|
||||
balken = "█" * gefuellt + "░" * ungefuellt
|
||||
return f"[{balken}] {wert:3d}% {farbe}"
|
||||
|
||||
|
||||
def zeige_tamagotchi_dashboard(pet):
|
||||
print("\n" + "=" * 45)
|
||||
print(f" 🥚 VIRTUAL PET: {pet.name.upper()} 🥚")
|
||||
print(f" Tierart: {pet.tierart} | Alter: {pet.alter_tage} Tag(e)")
|
||||
print("=" * 45)
|
||||
|
||||
if not pet.ist_lebendig:
|
||||
print("\n 🪦 R. I. P. 🪦")
|
||||
print(f" {pet.name} ist leider verstorben...")
|
||||
print("=" * 45 + "\n")
|
||||
return
|
||||
|
||||
print(f" 🍖 Hunger: {zeichne_balken(pet.hunger, umgekehrt=True)}")
|
||||
print(f" 💤 Müdigkeit: {zeichne_balken(pet.muedigkeit, umgekehrt=True)}")
|
||||
print(f" 💖 Glück: {zeichne_balken(pet.glueck)}")
|
||||
print("=" * 45)
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "*" * 50)
|
||||
print(" WILLKOMMEN BEIM TAMAGOTCHI SIMULATOR!")
|
||||
print("*" * 50)
|
||||
|
||||
name = input("\nWie soll dein Haustier heißen? (z.B. Yoshi): ").strip()
|
||||
if not name:
|
||||
name = "Yoshi"
|
||||
|
||||
tierart = input(f"Welche Tierart ist {name}? (z.B. Drache / Katze / Pinguin): ").strip()
|
||||
if not tierart:
|
||||
tierart = "Drache"
|
||||
|
||||
pet = Tamagotchi(name, tierart)
|
||||
print(f"\n🎉 Herzlichen Glückwunsch! {pet.name} das {pet.tierart}-Baby ist geschlüpft!\n")
|
||||
|
||||
while pet.ist_lebendig:
|
||||
zeige_tamagotchi_dashboard(pet)
|
||||
print("\nWas möchtest du tun?")
|
||||
print(" [1] 🍎 Füttern")
|
||||
print(" [2] ⚽ Spielen")
|
||||
print(" [3] 💤 Schlafen legen")
|
||||
print(" [4] ⏳ Nichts tun (Zeit vergeht)")
|
||||
print(" [q] 🚪 Spiel beenden")
|
||||
|
||||
wahl = input("\nDeine Wahl (1/2/3/4/q): ").strip().lower()
|
||||
|
||||
nachricht = ""
|
||||
if wahl == "1":
|
||||
nachricht = pet.fuettern()
|
||||
elif wahl == "2":
|
||||
nachricht = pet.spielen()
|
||||
elif wahl == "3":
|
||||
nachricht = pet.schlafen()
|
||||
elif wahl == "4":
|
||||
nachricht = f"Du schaust {pet.name} eine Weile beim Dösen zu..."
|
||||
elif wahl == "q":
|
||||
print(f"\nTschüss! Pass gut auf {pet.name} auf!\n")
|
||||
break
|
||||
else:
|
||||
print("❌ Ungültige Eingabe, bitte wähle 1, 2, 3, 4 oder q.")
|
||||
continue
|
||||
|
||||
# Wenn eine Aktion ausgeführt wurde, vergeht auch etwas Zeit:
|
||||
if wahl in ("1", "2", "4"):
|
||||
pet.zeit_vergeht()
|
||||
|
||||
print("\n👉 " + nachricht)
|
||||
time.sleep(0.5)
|
||||
|
||||
if not pet.ist_lebendig:
|
||||
zeige_tamagotchi_dashboard(pet)
|
||||
print("Game Over! Versuche es beim nächsten Mal besser zu versorgen.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nSpiel abgebrochen. Auf Wiedersehen!")
|
||||
111
06_abschlussprojekt_tamagotchi/test_aufgabe.py
Normal file
111
06_abschlussprojekt_tamagotchi/test_aufgabe.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import unittest
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
import aufgabe
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
||||
|
||||
|
||||
class TestKapitel06(unittest.TestCase):
|
||||
|
||||
def test_01_init(self):
|
||||
"""Prüft die Standard-Startwerte eines neuen Tamagotchis."""
|
||||
t = aufgabe.Tamagotchi("Pikachu", "Maus")
|
||||
self.assertEqual(t.name, "Pikachu")
|
||||
self.assertEqual(t.tierart, "Maus")
|
||||
self.assertEqual(t.hunger, 50)
|
||||
self.assertEqual(t.muedigkeit, 20)
|
||||
self.assertEqual(t.glueck, 80)
|
||||
self.assertEqual(t.alter_tage, 0)
|
||||
self.assertTrue(t.ist_lebendig)
|
||||
|
||||
def test_02_fuettern(self):
|
||||
"""Prüft die Auswirkungen des Fütterns."""
|
||||
t = aufgabe.Tamagotchi("Yoshi")
|
||||
t.hunger = 50
|
||||
t.muedigkeit = 20
|
||||
t.glueck = 80
|
||||
|
||||
text = t.fuettern(20)
|
||||
self.assertEqual(t.hunger, 30, "Hunger sollte um 20 sinken!")
|
||||
self.assertEqual(t.glueck, 85, "Glück sollte um 5 steigen!")
|
||||
self.assertEqual(t.muedigkeit, 25, "Müdigkeit sollte um 5 steigen!")
|
||||
self.assertIn("Yoshi", text)
|
||||
|
||||
# Hunger darf nicht negativ werden
|
||||
t.fuettern(100)
|
||||
self.assertEqual(t.hunger, 0, "Hunger darf nicht unter 0 fallen!")
|
||||
|
||||
def test_03_spielen(self):
|
||||
"""Prüft die Auswirkungen des Spielens und die Müdigkeitsgrenze."""
|
||||
t = aufgabe.Tamagotchi("Sparky")
|
||||
t.hunger = 30
|
||||
t.muedigkeit = 20
|
||||
t.glueck = 50
|
||||
|
||||
text = t.spielen(25)
|
||||
self.assertEqual(t.glueck, 75)
|
||||
self.assertEqual(t.hunger, 45)
|
||||
self.assertEqual(t.muedigkeit, 40)
|
||||
self.assertIn("Sparky", text)
|
||||
|
||||
# Zu müde zum Spielen (muedigkeit > 80)
|
||||
t.muedigkeit = 85
|
||||
t.glueck = 50
|
||||
text_muede = t.spielen(20)
|
||||
self.assertIn("zu müde", text_muede.lower())
|
||||
self.assertEqual(t.glueck, 50, "Werte dürfen sich bei Müdigkeit nicht ändern!")
|
||||
|
||||
def test_04_schlafen(self):
|
||||
"""Prüft Schlafen und Altern."""
|
||||
t = aufgabe.Tamagotchi("Luna")
|
||||
t.muedigkeit = 70
|
||||
t.hunger = 20
|
||||
t.alter_tage = 2
|
||||
|
||||
t.schlafen()
|
||||
self.assertEqual(t.muedigkeit, 0, "Müdigkeit sollte auf 0 zurückgesetzt werden!")
|
||||
self.assertEqual(t.hunger, 35, "Hunger sollte um 15 steigen!")
|
||||
self.assertEqual(t.alter_tage, 3, "Alter in Tagen sollte um 1 steigen!")
|
||||
|
||||
def test_05_zeit_vergeht_und_lebenszyklus(self):
|
||||
"""Prüft Verstreichen der Zeit und Ableben bei 100 Hunger oder 0 Glück."""
|
||||
t = aufgabe.Tamagotchi("Rex")
|
||||
t.hunger = 80
|
||||
t.muedigkeit = 30
|
||||
t.glueck = 20
|
||||
|
||||
lebendig = t.zeit_vergeht()
|
||||
self.assertTrue(lebendig)
|
||||
self.assertEqual(t.hunger, 90)
|
||||
self.assertEqual(t.muedigkeit, 40)
|
||||
self.assertEqual(t.glueck, 10)
|
||||
|
||||
# Noch ein Zeitschritt -> Hunger erreicht 100
|
||||
lebendig2 = t.zeit_vergeht()
|
||||
self.assertFalse(lebendig2)
|
||||
self.assertFalse(t.ist_lebendig)
|
||||
|
||||
# Aktionen an totem Tamagotchi
|
||||
reaktion = t.fuettern()
|
||||
self.assertIn("reagiert nicht mehr", reaktion)
|
||||
|
||||
def test_06_str_darstellung(self):
|
||||
"""Prüft die __str__ Repräsentation."""
|
||||
t = aufgabe.Tamagotchi("Bobo", "Affe")
|
||||
str_lebendig = str(t)
|
||||
self.assertIn("Bobo", str_lebendig)
|
||||
self.assertIn("Affe", str_lebendig)
|
||||
self.assertIn("50/100", str_lebendig)
|
||||
|
||||
t.ist_lebendig = False
|
||||
str_tot = str(t)
|
||||
self.assertIn("RIP", str_tot)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
53
README.md
Normal file
53
README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# 🐍 Python OOP Übungsreihe: Der Einstieg in Klassen & Objekte
|
||||
|
||||
Willkommen zu deiner Übungsreihe für **Objektorientierte Programmierung (OOP)** in Python! 🎉
|
||||
|
||||
Hier lernst du Schritt für Schritt, wie man mit Klassen arbeitet, warum sie die Programmierung übersichtlicher machen und wie du deine eigenen, mächtigen Objekte baust.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Der Lernpfad
|
||||
|
||||
Die Übungen sind so aufgebaut, dass jedes Kapitel auf dem vorherigen aufbaut:
|
||||
|
||||
| Kapitel | Thema | Was du lernst |
|
||||
| :--- | :--- | :--- |
|
||||
| **[01. Einstieg in Klassen](./01_einstieg_klassen/)** | Warum Klassen? | Bauplan vs. Objekt, Instanziierung & Attribute |
|
||||
| **[02. Konstruktor & self](./02_init_und_self/)** | `__init__` & `self` | Objekte sauber mit Startwerten initialisieren |
|
||||
| **[03. Methoden & Verhalten](./03_methoden_und_verhalten/)** | Methoden & Interaktion | Objekte können handeln & miteinander interagieren |
|
||||
| **[04. Schöne Textausgabe](./04_str_und_darstellung/)** | `__str__` Dunder-Methode | Wie Objekte lesbar mit `print()` ausgegeben werden |
|
||||
| **[05. Objekte kombinieren](./05_objekte_kombinieren/)** | Komposition | Listen von Objekten & komplexe Datenstrukturen |
|
||||
| **[06. Abschlussprojekt](./06_abschlussprojekt_tamagotchi/)** | Mini-Projekt: Tamagotchi | Ein interaktives Konsolenspiel mit allem Gelernten |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Aufbau eines Kapitels
|
||||
|
||||
In jedem Kapitelordner findest du:
|
||||
1. **`README.md`**: Eine verständliche Erklärung mit Analogien aus dem Alltag und Codebeispielen.
|
||||
2. **`aufgabe.py`**: Dein Arbeitsblatt! Hier steht der Starter-Code mit klaren `TODO`-Kommentaren.
|
||||
3. **`test_aufgabe.py`**: Automatische Tests, mit denen du deine Lösung jederzeit überprüfen kannst.
|
||||
4. **`musterloesung.py`**: Eine ausführlich kommentierte Beispiellösung (falls du mal nicht weiterkommst oder vergleichen möchtest).
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Wie du vorgehst
|
||||
|
||||
1. Öffne den Ordner des aktuellen Kapitels (z.B. `01_einstieg_klassen`).
|
||||
2. Lies dir die Erklärung in der jeweiligen `README.md` durch.
|
||||
3. Öffne `aufgabe.py` und bearbeite die nummerierten `TODO`s.
|
||||
4. Führe die Tests aus, um deine Lösung zu prüfen:
|
||||
```bash
|
||||
# Aus dem Hauptverzeichnis:
|
||||
python3 -m unittest 01_einstieg_klassen/test_aufgabe.py
|
||||
|
||||
# Oder direkt im Ordner:
|
||||
cd 01_einstieg_klassen
|
||||
python3 test_aufgabe.py
|
||||
```
|
||||
5. Wenn alle Tests mit einem grünen `OK` durchlaufen: **Herzlichen Glückwunsch!** Du bist bereit für das nächste Kapitel! 🏆
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tipp zum Lernen
|
||||
Versuche zuerst selbst eine Lösung zu finden. Wenn ein Fehler auftritt, lies die Fehlermeldung der Tests aufmerksam durch – sie gibt dir oft schon den entscheidenden Hinweis! Wenn du gar nicht weiterkommst, wirf einen Blick in `musterloesung.py` und versuche zu verstehen, wie das Problem gelöst wurde.
|
||||
73
test_all.py
Normal file
73
test_all.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
🧪 Gesamttest-Runner für alle Kapitel
|
||||
===================================
|
||||
Führe dieses Skript aus, um zu sehen, welche Kapitel du bereits erfolgreich gelöst hast:
|
||||
python3 test_all.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
KAPITEL = [
|
||||
("Kapitel 01: Einstieg in Klassen", "01_einstieg_klassen"),
|
||||
("Kapitel 02: Konstruktor & self", "02_init_und_self"),
|
||||
("Kapitel 03: Methoden & Verhalten", "03_methoden_und_verhalten"),
|
||||
("Kapitel 04: __str__ & Darstellung", "04_str_und_darstellung"),
|
||||
("Kapitel 05: Objekte kombinieren", "05_objekte_kombinieren"),
|
||||
("Kapitel 06: Abschlussprojekt Tamagotchi", "06_abschlussprojekt_tamagotchi"),
|
||||
]
|
||||
|
||||
def main():
|
||||
root = Path(__file__).parent.resolve()
|
||||
print("=" * 60)
|
||||
print("🎓 PYTHON OOP ÜBUNGSREIHE – GESAMTFORTSCHRITT")
|
||||
print("=" * 60)
|
||||
|
||||
gesamt_bestanden = 0
|
||||
|
||||
for titel, ordner in KAPITEL:
|
||||
kapitel_pfad = root / ordner
|
||||
if not kapitel_pfad.exists():
|
||||
continue
|
||||
|
||||
sys.path.insert(0, str(kapitel_pfad))
|
||||
|
||||
# Testsuite laden
|
||||
loader = unittest.TestLoader()
|
||||
suite = loader.discover(str(kapitel_pfad), pattern="test_aufgabe.py")
|
||||
|
||||
# Leisen Runner ausführen
|
||||
runner = unittest.TextTestRunner(stream=open("/dev/null", "w"), verbosity=0)
|
||||
result = runner.run(suite)
|
||||
|
||||
# Modul aus sys.path und sys.modules entfernen für saubere Isolation
|
||||
if str(kapitel_pfad) in sys.path:
|
||||
sys.path.remove(str(kapitel_pfad))
|
||||
if "aufgabe" in sys.modules:
|
||||
del sys.modules["aufgabe"]
|
||||
if "test_aufgabe" in sys.modules:
|
||||
del sys.modules["test_aufgabe"]
|
||||
|
||||
anzahl_tests = result.testsRun
|
||||
erfolgreich = result.wasSuccessful() and anzahl_tests > 0
|
||||
|
||||
if erfolgreich:
|
||||
print(f" ✅ {titel:42s} ({anzahl_tests}/{anzahl_tests} Tests OK)")
|
||||
gesamt_bestanden += 1
|
||||
else:
|
||||
fehlgeschlagen = len(result.failures) + len(result.errors)
|
||||
print(f" ⏳ {titel:42s} (Noch offen / {fehlgeschlagen} Fehler)")
|
||||
|
||||
print("=" * 60)
|
||||
print(f"Ergebnis: {gesamt_bestanden} von {len(KAPITEL)} Kapiteln vollständig gelöst!")
|
||||
if gesamt_bestanden == len(KAPITEL):
|
||||
print("🏆 PERFEKT! Du hast alle OOP-Aufgaben mit Bravour gemeistert! 🎉")
|
||||
else:
|
||||
print("💡 Öffne das nächste offene Kapitel und starte mit aufgabe.py!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue