Zum Inhalt
wannacode

Klassen und Objekte

Kurz gesagt: Eine Klasse beschreibt einen neuen Typ mit Attributen, und ein Objekt ist ein konkretes Exemplar davon, erzeugt mit einem Aufruf wie Product(); die Variable zeigt nur auf das Objekt.

Problem

Die Preise sind durcheinander

Du schreibst den Warenkorb eines Onlineshops. Jedes Produkt hat einen Namen und einen Preis, also speicherst du sie in zwei Listen: An Index 0 stehen der Name und der Preis des ersten Produkts, an Index 1 die des zweiten.

Du willst das Teuerste ganz vorne haben und tauschst die Preise:

names = ["Brot", "Milch", "Kaffee"]
prices = [1.20, 2.10, 7.50]
# Das Teuerste zuerst: den ersten und den dritten Preis tauschen.
prices[0], prices[2] = prices[2], prices[0]
for i in range(len(names)):
print(f"{names[i]}: {prices[i]} €")
Ausgabe
Brot: 7.5 €
Milch: 2.1 €
Kaffee: 1.2 €

Das Brot kostet jetzt 7,50 €. Die Preise sind getauscht, die Namen aber nicht. Die beiden Listen müssen sich immer gemeinsam ändern, aber nichts zwingt dich dazu. Kommt eine dritte Liste für die Stückzahlen dazu, musst du an drei denken.

Die Idee

Ein Produkt, ein Ort

Der Name und der Preis sind keine getrennten Dinge. Sie sind Teile eines Produkts. Wir wollen einen Typ „Produkt“, so wie es int für Zahlen und str für Text gibt.

So einen Typ machst du dir selbst. Er heißt Klasse:

class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0

self.name, self.price und self.quantity sind die Felder des Produkts (in Python heißen sie auch Attribute). Jedes Produkt hat seinen eigenen Namen, seinen eigenen Preis und seine eigene Stückzahl.

In Python zählst du die Felder nicht einzeln auf. Du erzeugst sie mit einem Startwert in __init__: leerer Text, 0.0 und 0. Die Zeile def __init__(self): schreibst du vorerst genau so. Der Code darunter läuft für jedes neue Produkt, und self ist genau dieses Produkt. Im Detail verstehst du das in der Lektion „Konstruktoren und self“.

Die Klasse ist nur eine Beschreibung: „Jedes Produkt hat einen Namen, einen Preis und eine Stückzahl.“ Ein Produkt gibt es noch nicht.

Der Name der Klasse beginnt mit einem Großbuchstaben (Product), die Namen der Felder mit einem Kleinbuchstaben (name, price). So siehst du sofort, was ein Typ und was ein Wert ist.

Die Idee

Product(): von der Beschreibung zum Produkt

Ein konkretes Produkt erzeugst du aus der Klasse, indem du die Klasse wie eine Funktion aufrufst: Product(). Es heißt Objekt. An seine Felder kommst du mit einem Punkt:

shop.py
class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0
bread = Product()
bread.name = "Brot"
bread.price = 1.20
bread.quantity = 2
coffee = Product()
coffee.name = "Kaffee"
coffee.price = 7.50
coffee.quantity = 1
print(f"{bread.name}: {bread.price * bread.quantity} €")
print(f"{coffee.name}: {coffee.price * coffee.quantity} €")
Ausgabe
Brot: 2.4 €
Kaffee: 7.5 €
  • Die Klasse steht über dem Code, der sie benutzt. Python liest die Datei von oben nach unten und muss sie schon gesehen haben. Eine Datei kann mehrere Klassen haben.
  • Python hat kein new. Product() erzeugt ein neues Objekt und gibt es zurück. Jedes Objekt hat eigene Felder: Das Brot und der Kaffee kommen sich nicht in die Quere.
  • Vor bread schreibst du keinen Typ, aber das Objekt hat trotzdem einen Typ: type(bread) liefert <class '__main__.Product'>.
  • bread.price ist „der Preis des Brots“. Du liest und schreibst ihn wie eine gewöhnliche Variable.

Jetzt stehen der Name und der Preis des Produkts an einem Ort. Verschiebst du das Produkt, wandern sie mit.

Probier es aus

Nichts ist ausgefüllt

Wenn du ein Produkt erzeugst und nichts ausfüllst, was steht dann in seinen Feldern?

Was gibt das Programm aus?

item = Product()
print(item.name)
print(item.price)
print(item.quantity)
Antwort

Probier es aus

Was fehlt?

Was gehört in die Lücke, damit es ein neues Produkt gibt?

milk = ()
milk.name = "Milch"
milk.price = 2.10

Sieh es dir an

Die Variable zeigt auf das Objekt

Du willst den Kaffee im Angebot verkaufen, also machst du eine „Kopie“, um seinen Preis zu ändern:

coffee = Product()
coffee.price = 10
sale = coffee
sale.price = 8
print(coffee.price) # 8
Schritt für Schritt, in Worten
  1. Product() erzeugt das Objekt, und __init__ gibt seinen Feldern Startwerte. coffee bekommt einen Pfeil darauf.
  2. Dem Pfeil von coffee nach: Der Preis des Objekts wird 10.
  3. sale = coffee kopiert den Pfeil, nicht das Objekt. Zwei Pfeile, ein Objekt.
  4. Dem Pfeil von sale nach: Der Preis im selben Objekt wird 8.
  5. coffee zeigt auf dasselbe Objekt, also wird 8 ausgegeben.

Der Preis des Kaffees ist auch 8 geworden. Der Grund: Die Variable coffee ist nicht das Objekt selbst. Das Objekt liegt getrennt im Speicher, und coffee merkt sich, wo es ist: einen Pfeil auf das Objekt. So eine Variable heißt Referenz.

sale = coffee kopiert den Pfeil, nicht das Objekt. Jetzt zeigen zwei Pfeile auf ein Objekt, deshalb sieht man die Änderung über sale auch über coffee.

Dasselbe hast du in der Lektion über Listen gesehen: sale = prices gab derselben Liste einen zweiten Namen. Eine Liste ist auch ein Objekt.

Die Idee

Der Rabatt funktioniert jetzt

Im Modul „Funktionen“ hat der Rabatt nicht funktioniert: Die Funktion bekam den Preis, änderte ihren eigenen Parameter, und die Änderung ging verloren. Mit einem Objekt ist das anders:

def discount(p, percent):
p.price = p.price * (100 - percent) / 100
coffee = Product()
coffee.price = 10
discount(coffee, 10)
print(coffee.price)
Ausgabe
9.0

Der Parameter ist immer noch eine Kopie, aber eine Kopie des Pfeils. Er zeigt auf dasselbe Objekt, deshalb bleibt die Änderung an p.price auch nach der Funktion erhalten.

Genauer, für Neugierige

Wenn die Funktion ein neues Objekt in den Parameter legt, verschiebt sie nur ihre eigene Kopie des Pfeils. Das Objekt draußen ändert sich nicht:

def reset(p):
p = Product()
p.price = 0
coffee = Product()
coffee.price = 10
reset(coffee)
print(coffee.price)
Ausgabe
10
Deshalb sagt man, dass Python alles auf dieselbe Weise übergibt wie =: Der Parameter bekommt einen Pfeil auf dasselbe Objekt. Wenn die Funktion ihren Pfeil woandershin richtet, kommt das bei der Variable draußen nicht an.

Die Idee

None: ein Pfeil ins Nichts

Eine Variable kann auf gar kein Objekt zeigen. Dann steht in ihr None: „nichts“. Wenn du über sie ein Feld lesen willst, gibt es kein Objekt, aus dem du es lesen kannst:

gift = None
print(gift.name)

Das Programm startet, bricht aber mit einem Fehler ab, sobald es diese Zeile erreicht:

Fehler
AttributeError: 'NoneType' object has no attribute 'name'

Das ist der häufigste Fehler mit Objekten. Was eine „Exception“ ist und wie man sie fängt, siehst du im Modul „Fehler und Exceptions“.

Auch eine Liste speichert Pfeile, nicht die Produkte selbst. [None] * 3 macht drei Plätze, die noch auf kein Produkt zeigen. Jeder Platz ist None, bis du dort Product() hineinlegst:

cart = [None] * 3
print(cart[0] is None)
cart[0] = Product()
cart[0].name = "Brot"
print(cart[0].name)
Ausgabe
True
Brot

Probier es aus

Finde den Fehler

Das Programm soll „Brot“ und „Milch“ ausgeben. Stattdessen gibt es zweimal „Milch“ aus. In welcher Zeile steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Übung

Deine Klassen

Löse die Aufgaben in deinem Editor. Die Klasse steht ganz oben in der Datei, und die Objekte erzeugst du im Code darunter.

1. Buch. Eine Klasse Book mit Feldern für den Titel und den Autor. Erzeuge zwei Bücher und gib sie aus.

Ausgabe
Der Prozess von Franz Kafka
Effi Briest von Theodor Fontane

2. Auto. Eine Klasse SimpleCar mit einem Feld für die Geschwindigkeit. Erzeuge ein Auto (seine Geschwindigkeit ist 0), erhöhe die Geschwindigkeit um 10 und gib sie aus. In der Lektion „Instanzmethoden“ wird daraus eine Methode des Autos.

Ausgabe
Geschwindigkeit nach dem Beschleunigen: 10

3. Der Warenkorb. Der Warenkorb von der ersten Karte, diesmal mit einer Liste aus drei Product-Objekten: Brot 1,20 € (2 Stück), Milch 2,10 € (1 Stück), Kaffee 7,50 € (1 Stück). Gib den Betrag für jede Zeile und die Gesamtsumme aus. Erzeuge jedes Produkt zuerst mit Product() und lege es dann in die Liste.

Ausgabe
Brot x2: 2.4 €
Milch x1: 2.1 €
Kaffee x1: 7.5 €
Summe: 12.0 €

4. Das Teuerste zuerst. Sortiere denselben Warenkorb nach Preis, vom teuersten zum billigsten, mit dem Bubblesort aus der Lektion über Listen. Diesmal tauschst du ganze Produkte, deshalb wandern der Name und der Preis zusammen.

Ausgabe
Kaffee: 7.5 €
Milch: 2.1 €
Brot: 1.2 €

Weitere Aufgaben

  • Schüler. Eine Klasse Student mit einem Namen und drei Noten (drei Felder). Erzeuge zwei Schüler und gib die Durchschnittsnote von jedem aus.
  • Ein Buch, zwei Namen. Erzeuge ein Buch, speichere es in zwei Variablen und ändere den Titel über die zweite. Gib den Titel über die erste aus. Erkläre in Worten, warum das so ist.

Test

Teste dich

1. Was ist der Unterschied zwischen einer Klasse und einem Objekt?
2. sale = coffee sale.price = 8 Was passiert mit dem Preis von coffee?
3. Was steht in der Liste direkt nach cart = [None] * 3?
4. Eine Funktion bekommt ein Produkt als Parameter und ändert seinen Preis. Siehst du die Änderung nach dem Aufruf?
5. gift = None Was passiert bei gift.name?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·