Abschnitt 14 · Lektion 4
Datenklassen
Der Artikel mit Namen, Preis und Bestand ist eine Klasse, die vor allem Daten trägt. Für so eine Klasse schreibt man in Python erstaunlich viel, und zwar immer wieder dasselbe. Eine Sache fehlt trotzdem noch.
Gleich, aber nicht dasselbe
class Artikel:
def __init__(self, name, preis, bestand=0):
self.name = name
self.preis = preis
self.bestand = bestand
def __repr__(self):
return f"Artikel({repr(self.name)}, {self.preis}, {self.bestand})"
brot = Artikel("Brot", 2.49, 12)
noch_ein_brot = Artikel("Brot", 2.49, 12)
print(brot, noch_ein_brot)
print(brot == noch_ein_brot)
print(brot == brot) Zwei Brote mit demselben Namen, demselben Preis und demselben Bestand, und == sagt False. Ohne
weitere Angabe fragt == bei Objekten nur, ob es dasselbe Objekt ist. Ob zwei Objekte dieselben
Werte haben, ist dabei egal. Das Brot ist sich selbst gleich, dem zweiten Brot nicht. Für einen
Artikel, der nur aus seinen Werten besteht, ist das die falsche Antwort.
Beheben ließe sich das mit einer weiteren Methode mit Unterstrichen, __eq__. Dann stünde aber
jedes Attribut an vier Stellen in der Klasse, nämlich als Parameter von __init__, in der Zuweisung
an self, in __repr__ und im Vergleich. Bei drei Attributen geht das noch. Bei acht vergisst man
irgendwo eins.
@dataclass
from dataclasses import dataclass
@dataclass
class Artikel:
name: str
preis: float
bestand: int = 0
brot = Artikel("Brot", 2.49, 12)
butter = Artikel("Butter", 2.29)
print(brot)
print(butter)
print(brot == Artikel("Brot", 2.49, 12))
brot.bestand = brot.bestand - 3
print(brot.bestand) Bei einer Datenklasse (dataclass) entstehen diese Methoden von selbst. In der Klasse stehen nur noch
die Felder (fields), also die Attribute, die jedes Objekt bekommt. Jedes hat einen Typhinweis wie in
Lektion 8.6, und ein Standardwert geht wie bei einem Parameter: bestand: int = 0.
Daraus wird ein __init__ mit genau diesen Parametern in genau dieser Reihenfolge, dazu ein
__repr__, der jedes Feld beim Namen nennt, und ein ==, das die Werte vergleicht. Die Butter ohne
Bestand hat 0 Stück. Das Brot ist einem neuen Brot mit denselben Werten gleich, und geändert wird
ein Feld wie jedes andere Attribut.
@dataclass über der Klasse ist ein Dekorator (decorator). Damit bekommt die Klasse etwas
angehängt, das sie sonst nicht könnte. Mehr braucht es hier nicht, das @ kommt bei pytest und bei
Flask wieder.
Die Typhinweise prüft Python übrigens auch hier nicht. Artikel("Brot", "2,49") legt einfach einen
Artikel an, dessen Preis ein Text ist. Erst eine Rechnung damit geht schief, denn mal zwei ergibt
2,492,49. Die Typhinweise sagen, was gemeint ist, für jeden, der die Klasse liest. Auch für dich
selbst in drei Monaten.
Ohne Typhinweis kein Feld
from dataclasses import dataclass
@dataclass
class Artikel:
name: str
preis: float
bestand = 0
brot = Artikel("Brot", 2.49, 12)
print(brot) Beim Bestand fehlt diesmal : int. Ohne Typhinweis ist bestand = 0 kein Feld, sondern ein
gewöhnlicher Wert der Klasse, und __init__ kennt nur zwei Parameter. Auf drei Werte antwortet
Python deshalb mit TypeError: Artikel.__init__() takes 3 positional arguments but 4 were given.
Gezählt wird wie bei den Methoden mit self. Erlaubt sind also drei, und mit self waren es vier.
Ob etwas ein Feld ist, hängt in einer Datenklasse also am Typhinweis. Fehlt dagegen der ganze Wert
und steht nur bestand da, meldet Python schon beim Lesen der Klasse
NameError: name 'bestand' is not defined.
Nach einem Feld sortieren
from dataclasses import dataclass
@dataclass
class Artikel:
name: str
preis: float
bestand: int = 0
regal = [
Artikel("Milch", 1.19, 30),
Artikel("Brot", 2.49, 12),
Artikel("Butter", 2.29, 8),
]
for artikel in sorted(regal, key=lambda artikel: artikel.preis):
print(artikel)
print(max(regal, key=lambda artikel: artikel.bestand).name) Sortieren geht mit dem key aus Lektion 8.7. sorted(regal) allein endet mit
TypeError: '<' not supported between instances of 'Artikel' and 'Artikel'. Ob die Milch vor dem
Brot kommt, weiß Python nicht, es könnte ja nach jedem Feld gehen. Mit
key=lambda artikel: artikel.preis ist es gesagt.
max mit demselben Muster findet den Artikel mit dem größten Bestand. Zurück kommt dabei der ganze
Artikel und nicht nur die Zahl. Deshalb steht .name dahinter.
Was die Aufgabe will
Eine Zeile aus dem Kontoauszug ist bisher ein Dictionary mit Text darin. Mit einer Datenklasse wird aus jeder Zeile eine Buchung, mit einem echten Datum und einem echten Betrag.
Zum Mitnehmen
Mit @dataclass entstehen __init__, __repr__ und der Vergleich mit == aus den Feldern der Klasse. Ein Feld ist nur, was einen Typhinweis hat, etwa bestand: int = 0 mit Standardwert. Sortiert wird eine Liste solcher Objekte mit key=lambda artikel: artikel.preis, dann stehen sie in der Reihenfolge der Preise.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.