Skip to content

Commit 6c7f6fc

Browse files
committed
ueberarbeitete
1 parent 31b85f9 commit 6c7f6fc

1 file changed

Lines changed: 124 additions & 45 deletions

File tree

07a_OOPGrundlagenPython.md

Lines changed: 124 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
<!--
22
3-
author: Sebastian Zug, Galina Rudolf, André Dietrich
3+
author: Sebastian Zug, Galina Rudolf, André Dietrich, Volker Göhler
44
email: sebastian.zug@informatik.tu-freiberg.de
5-
version: 1.0.0
5+
version: 1.0.1
66
language: de
77
narrator: Deutsch Female
8-
comment: Konzeptioneller Einstieg in die Objektorientierung anhand von Python — für Teilnehmer ohne C#-Hintergrund. Klassen, Objekte, Konstruktor, Methoden, Vererbung.
9-
tags:
10-
logo:
8+
comment: Konzeptioneller Einstieg in die Objektorientierung anhand von Python -- für Teilnehmer ohne C#-Hintergrund. Klassen, Objekte, Konstruktor, Methoden, Vererbung.
9+
tags: python, oop, objektorientierung, klasse, objekt, konstruktor, self, vererbung, super, kapselung
1110
1211
import: https://github.com/liascript/CodeRunner
1312
@@ -47,23 +46,10 @@ Nach der Vorlesung können Sie ...
4746
- den Unterschied zwischen Klassenattributen und Instanzattributen benennen,
4847
- die ersten Schritte der **Kapselung** anwenden (`_`-Konvention).
4948

50-
### Zeitplan (90 Minuten)
51-
52-
| Zeit | Thema |
53-
| ---------- | ---------------------------------------------- |
54-
| 0 – 10 min | Motivation: Warum OOP? |
55-
| 10 – 20 min | Bauplan/Instanz, Begriffsbildung |
56-
| 20 – 35 min | Erste Klasse `Animal`, `__init__`, `self` |
57-
| 35 – 45 min | Klassen- vs. Instanzattribute, `__str__` |
58-
| 45 – 50 min | Pause |
59-
| 50 – 60 min | Komposition: `Farm` enthält `Animal`s |
60-
| 60 – 70 min | Kapselung mit `_`-Konvention |
61-
| 70 – 85 min | Vererbung, Override, `super()`, Polymorphie |
62-
| 85 – 90 min | Brücke zu C#, Aufgabenüberblick |
63-
6449
## Warum Objektorientierung?
6550

66-
### Ausgangsproblem
51+
Ausgangsproblem
52+
====================
6753

6854
Stellen Sie sich vor, Sie verwalten einen kleinen Bauernhof in einem Programm. Drei Tiere sollen gespeichert werden — jeweils mit Name, Geräusch und Alter.
6955

@@ -90,7 +76,7 @@ print(f"{name3} ({age3} Jahre) macht {sound3}")
9076

9177
> **Frage:** Was passiert, wenn der Bauernhof auf 50 Tiere wächst? Was, wenn sich „macht" in „sagt" ändert?
9278
93-
{{1-2}}
79+
{{1-2}}
9480
*******************************************************************************
9581

9682
Probleme dieses Ansatzes:
@@ -104,10 +90,12 @@ Probleme dieses Ansatzes:
10490
10591
*******************************************************************************
10692

107-
{{2-3}}
93+
{{2-4}}
10894
*******************************************************************************
10995

110-
### Erster Lösungsversuch: Dictionaries und Funktionen
96+
97+
Erster Lösungsversuch: Dictionaries und Funktionen
98+
====================
11199

112100
Bevor wir Klassen einführen, schauen wir uns an, wie weit man mit den bisher bekannten Mitteln kommt. Wir bündeln die Daten zu einem Tier in einem **Dictionary** und schreiben eine Funktion, die darauf arbeitet:
113101

@@ -125,11 +113,18 @@ make_noise(berta)
125113
```
126114
@LIA.eval(`["main.py"]`, `none`, `python3 main.py`)
127115

116+
> **Frage:** Sehen Sie hier schon die Idee von OOP? Was ist besser als vorher, und was könnte noch verbessert werden?
117+
118+
*******************************************************************************
119+
120+
{{3-4}}
121+
*******************************************************************************
122+
128123
Das ist schon deutlich besser. Aber drei Probleme bleiben:
129124

130-
1. **Keine Garantie, dass die Daten zusammenpassen.** Niemand hindert uns daran, ein Dictionary ohne `sound` zu bauen — der Fehler erscheint erst beim Aufruf.
125+
1. **Keine Garantie, dass die Daten zusammenpassen.** Niemand hindert uns daran, ein Dictionary ohne `sound` zu bauen. Der Fehler erscheint erst beim Aufruf.
131126
2. **Daten und Funktionen sind getrennt.** Wer das Dictionary benutzt, muss wissen, *welche* Funktionen dazu gehören. Das steht nirgends im Code.
132-
3. **Keine Bauplan-Beschreibung.** Was ein „Tier" ist, ergibt sich nur indirekt aus den Schlüsseln, die zufällig benutzt werden.
127+
3. **Keine Bauplan-Beschreibung.** Was ein „Tier" ist, ergibt sich nur indirekt aus den Keys, die benutzt werden.
133128

134129
> **Frage:** Wie könnte eine Sprache uns helfen, Daten und passende Funktionen *zusammenzuhalten* und einen *Bauplan* zu beschreiben, an den sich alle Tiere halten müssen?
135130
@@ -163,7 +158,8 @@ style="width: 100%; max-width: 700px; display: block; margin-left: auto; margin-
163158
164159
## Die erste Klasse
165160

166-
### Aufbau
161+
Aufbau
162+
==========
167163

168164
In Python schreiben wir den Bauplan mit dem Schlüsselwort `class`:
169165

@@ -183,7 +179,7 @@ class Animal:
183179
Drei Bestandteile sind neu und brauchen eine Erklärung:
184180

185181
- **`__init__`** — der **Konstruktor**. Wird *einmal* beim Erzeugen eines Objekts aufgerufen. Hier werden die Attribute initialisiert.
186-
- **`self`** — der Verweis auf *dieses konkrete Objekt*. (In C# heißt das später `this`.) Er muss als erster Parameter jeder Methode stehen.
182+
- **`self`** — der Verweis auf *dieses konkrete Objekt*. (In C# heißt es `this`.) Er muss als erster Parameter jeder Methode stehen. Ist aber nur eine Konvention.
187183
- **`self.name = name`** — legt das Attribut `name` *am Objekt* ab. Ohne `self.` wäre `name` nur eine lokale Variable in der Funktion.
188184

189185
### Objekte erzeugen und nutzen
@@ -244,10 +240,11 @@ kitty.have_birthday()
244240
245241
### Klassenattribute vs. Instanzattribute
246242

247-
Manche Eigenschaften gehören *zu jedem einzelnen Objekt* (jedes Tier hat einen anderen Namen). Andere Eigenschaften gehören *zur ganzen Klasse* — sie sind für alle Tiere gleich. Beispiel: Alle Tiere auf dem Planeten leben auf demselben Planeten.
243+
Manche Eigenschaften gehören *zu jedem einzelnen Objekt* (jedes Tier hat einen anderen Namen). Andere Eigenschaften gehören *zur ganzen Klasse* — sie sind für alle Tiere gleich. Beispiel: Alle Tiere atmen Luft und leben auf demselben Planeten.
248244

249245
```python ClassAttribute.py
250246
class Animal:
247+
atmen = "Luft" # Klassenattribut — gehört zur Klasse
251248
planet = "Erde" # Klassenattribut — gehört zur Klasse
252249

253250
def __init__(self, name, sound):
@@ -312,7 +309,7 @@ print(f"Mein Tier: {kitty}")
312309
```
313310
@LIA.eval(`["main.py"]`, `none`, `python3 main.py`)
314311

315-
> **Hintergrund:** Methoden, deren Namen in `__` eingerahmt sind (man spricht „Dunder-Methoden"), sind Haken, die Python automatisch aufruft. Wir werden in C# später ähnliche Mechanismen sehen — dort heißen sie `ToString()`, `Equals()` etc.
312+
> **Hintergrund:** Methoden, deren Namen in `__` eingerahmt sind (man spricht „Dunder-Methoden"), sind Hooks, die Python automatisch aufruft. Wir werden in C# später ähnliche Mechanismen sehen — dort heißen sie `ToString()`, `Equals()` etc.
316313
317314
## Mehrere Objekte zusammenführen
318315

@@ -356,7 +353,8 @@ farm.morning_call()
356353
357354
## Kapselung — Was darf nach außen sichtbar sein?
358355

359-
### Das Problem
356+
Das Problem
357+
====================
360358

361359
Bisher konnten wir auf jedes Attribut von außen direkt zugreifen — auch schreibend. Das ist gefährlich:
362360

@@ -406,17 +404,77 @@ print(f"{kitty.name} ist {kitty.get_age()} Jahre alt.")
406404

407405
| Schreibweise | Bedeutung |
408406
| -------------------- | ------------------------------------------------------------------------ |
409-
| `name` | öffentlich — jeder darf lesen/schreiben |
410-
| `_name` | „bitte nicht von außen anfassen" — nur für die Klasse selbst |
411-
| `__name` (zwei `_`) | erzwingt Namensumbenennung intern — selten gebraucht |
407+
| `name` | öffentlich — jeder darf lesen/schreiben (public) |
408+
| `_name` | „bitte nicht von außen anfassen" — nur für die Klasse selbst (protected) |
409+
| `__name` (zwei `_`) | erzwingt Namensumbenennung intern (private) |
412410

413411
> **Merke:** Kapselung trennt das *Was* (öffentliches Verhalten) vom *Wie* (interne Umsetzung). Wer die Klasse benutzt, soll nur das Was sehen müssen — wir können das Wie ändern, ohne dass Aufrufer kaputt gehen.
414412
415-
> In C# werden wir dafür echte Modifizierer (`public`, `private`) und sogenannte **Properties** kennenlernen — Vorlesung 07/08.
413+
> In C# werden wir dafür echte Modifizierer (`public`, `private`) und sogenannte **Properties** kennenlernen — Vorlesung 07/08. Das geht in Python auch — siehe nächster Slide.
414+
415+
### Python Properties: Elegante Kapselung mit `@property`
416+
417+
In Python können **Properties** verwendet werden, um den Zugriff auf Attribute zu kontrollieren, ohne die Syntax für den Zugriff zu ändern. Dies ermöglicht eine saubere Trennung zwischen öffentlicher Schnittstelle und interner Implementierung.
418+
419+
```python EncapsulationProperties.py
420+
class Animal:
421+
def __init__(self, name, age):
422+
self.name = name
423+
self._age = age # Internes Attribut
424+
425+
@property
426+
def age(self):
427+
"""Getter für das Alter."""
428+
return self._age
429+
430+
@age.setter
431+
def age(self, new_age):
432+
"""Setter für das Alter mit Validierung."""
433+
if new_age < 0:
434+
raise ValueError(f"Ungültiges Alter: {new_age}")
435+
self._age = new_age
436+
437+
@age.deleter
438+
def age(self):
439+
"""Löscht das Alter."""
440+
print(f"Alter von {self.name} wird gelöscht.")
441+
del self._age
442+
443+
# Verwendung
444+
kitty = Animal("Kitty", 5)
445+
print(f"{kitty.name} ist {kitty.age} Jahre alt.") # Getter
446+
447+
kitty.age = 6 # Setter
448+
print(f"{kitty.name} ist {kitty.age} Jahre alt.")
449+
450+
try:
451+
kitty.age = -100 # Wird abgelehnt
452+
except ValueError as e:
453+
print(e)
454+
455+
del kitty.age # Deleter
456+
```
457+
@LIA.eval(`["EncapsulationProperties.py"]`, `none`, `python3 EncapsulationProperties.py`)
458+
459+
---
460+
461+
| Dekorator | Bedeutung |
462+
|--------------------|---------------------------------------------------------------------------|
463+
| `@property` | Definiert den **Getter** für ein Attribut. |
464+
| `@<property>.setter` | Definiert den **Setter** für ein Attribut (mit Validierung). |
465+
| `@<property>.deleter` | Definiert den **Deleter** für ein Attribut. |
466+
467+
---
468+
> **Vorteil von Properties:**
469+
>
470+
> - Der Zugriff auf Attribute bleibt **intuitiv** (`obj.age` statt `obj.get_age()`).
471+
> - Die **Validierung** und **Logik** bleiben in der Klasse verborgen.
472+
> - Änderungen an der internen Implementierung (z. B. Berechnung von `_age`) haben **keine Auswirkungen** auf den Code, der die Klasse nutzt.
416473
417474
## Vererbung
418475

419-
### Motivation
476+
Motivation
477+
====================
420478

421479
Auf unserem Bauernhof sollen verschiedene Tierarten leben. Ein **Hund** kann zusätzlich `fetch()` (Stöckchen apportieren), eine **Kuh** liefert `milk()`. Beide sind aber *immer noch Tiere* — sie haben Namen, Alter, Geräusch.
422480

@@ -490,6 +548,8 @@ class Cat(Animal):
490548
# Wir überschreiben die Methode aus Animal
491549
print(f"{self.name} macht {self.sound} ... und schnurrt.")
492550

551+
animal = Animal("Berta", "Muuh", 8)
552+
animal.make_noise() # Berta macht Muuh
493553

494554
kitty = Cat("Kitty", "Miau", 5)
495555
kitty.make_noise()
@@ -532,7 +592,7 @@ kitty.make_noise()
532592

533593
### Eine gemeinsame Liste verschiedener Tiere
534594

535-
Weil `Dog` und `Cat` *beide* `Animal`s sind, dürfen sie in derselben Liste stehen — und beim Aufruf von `make_noise()` reagiert jedes auf seine eigene Art.
595+
Da `Dog` und `Cat` *beide* `Animal`s sind, haben sie die `make_noise` Methode und wir können diese aufrufen und erwarten, dass jedes Tier das passende Geräusch macht.
536596

537597
```python Polymorphism.py
538598
class Animal:
@@ -633,12 +693,33 @@ Zum Abschluss verbinden wir alle Konzepte in einem etwas größeren Beispiel. Ei
633693
class Product:
634694
def __init__(self, name, price, stock):
635695
self.name = name
636-
self._price = price # intern, über Methoden zugreifen
637-
self._stock = stock
696+
self._price = price # Intern, über Property zugreifen
697+
self._stock = stock # Intern, über Property zugreifen
638698

639-
def get_price(self):
699+
@property
700+
def price(self):
701+
"""Getter für den Preis."""
640702
return self._price
641703

704+
@price.setter
705+
def price(self, new_price):
706+
"""Setter für den Preis mit Validierung."""
707+
if new_price < 0:
708+
raise ValueError(f"Ungültiger Preis: {new_price}")
709+
self._price = new_price
710+
711+
@property
712+
def stock(self):
713+
"""Getter für den Bestand."""
714+
return self._stock
715+
716+
@stock.setter
717+
def stock(self, new_stock):
718+
"""Setter für den Bestand mit Validierung."""
719+
if new_stock < 0:
720+
raise ValueError(f"Ungültiger Bestand: {new_stock}")
721+
self._stock = new_stock
722+
642723
def is_available(self, amount):
643724
return self._stock >= amount
644725

@@ -649,8 +730,7 @@ class Product:
649730
return True
650731

651732
def __str__(self):
652-
return f"{self.name} ({self._price:.2f} €, {self._stock} auf Lager)"
653-
733+
return f"{self.name} ({self.price:.2f} €, {self.stock} auf Lager)"
654734

655735
class DigitalProduct(Product):
656736
def __init__(self, name, price, size_mb):
@@ -659,8 +739,7 @@ class DigitalProduct(Product):
659739
self.size_mb = size_mb
660740

661741
def __str__(self):
662-
return f"{self.name} (digital, {self._price:.2f} €, {self.size_mb} MB)"
663-
742+
return f"{self.name} (digital, {self.price:.2f} €, {self.size_mb} MB)"
664743

665744
class Cart:
666745
def __init__(self):
@@ -674,15 +753,14 @@ class Cart:
674753
print(f" ! {product.name} nicht ausreichend verfügbar")
675754

676755
def total(self):
677-
return sum(p.get_price() * n for p, n in self.items)
756+
return sum(p.price * n for p, n in self.items)
678757

679758
def show(self):
680759
print("Warenkorb:")
681760
for product, amount in self.items:
682761
print(f" - {amount} x {product}")
683762
print(f" = Summe: {self.total():.2f}")
684763

685-
686764
buch = Product("OOP-Lehrbuch", 29.90, stock=3)
687765
ebook = DigitalProduct("OOP-Lehrbuch (PDF)", 14.90, size_mb=12)
688766
stift = Product("Bleistift", 0.80, stock=50)
@@ -758,6 +836,7 @@ In den Vorlesungen 07, 08 und 09 gehen wir tiefer:
758836
6. **Hierarchie modellieren.** Modellieren Sie eine Klassenhierarchie für Verkehrsmittel: `Vehicle` (mit `name` und Methode `move()`), darunter `Car`, `Bicycle`, `Boat`. Jede Klasse soll `move()` passend überschreiben (z. B. „rollt", „tritt", „schwimmt"). Legen Sie eine Liste aus drei verschiedenen Verkehrsmitteln an und rufen Sie `move()` in einer Schleife auf.
759837

760838
7. **Quizfragen zur Selbstkontrolle.**
839+
761840
- Was ist der Unterschied zwischen einer Klasse und einem Objekt?
762841
- Warum ist `self` als erster Parameter jeder Methode notwendig?
763842
- Wann verwenden Sie Vererbung, wann eher Komposition?

0 commit comments

Comments
 (0)