Was alle Objekte teilen: Klassenattribute
Kurz gesagt: Ein Klassenattribut gehört zur Klasse, nicht zum Objekt: Alle Objekte sehen einen gemeinsamen Wert, und @staticmethod und @classmethod sind Methoden ohne Objekt.
Problem
Jedes Produkt ist Nr. 1
Der Shop will, dass jedes neue Produkt die nächste Nummer bekommt: 1, 2, 3… Du legst einen Zähler _next_id in das Produkt. __init__ gibt dem Produkt die aktuelle Nummer und erhöht den Zähler:
class Product: def __init__(self, name): self._next_id = 1 self._name = name self._id = self._next_id self._next_id += 1
def __str__(self): return f"Nr. {self._id} {self._name}"
bread = Product("Brot")milk = Product("Milch")coffee = Product("Kaffee")print(bread)print(milk)print(coffee)Nr. 1 BrotNr. 1 MilchNr. 1 KaffeeAlle sind Nr. 1. Der Grund ist, dass _next_id ein Attribut des Objekts ist, genau wie der Name. Jedes neue Produkt bekommt seinen eigenen Zähler, der bei 1 beginnt. Der Zähler des Brotes kommt nie bei der Milch an.
Die Idee
Ein Attribut der Klasse, nicht des Objekts
Der Zähler gehört nicht zu einem Produkt. Alle Produkte teilen ihn sich. Schreib ihn in die Klasse selbst, außerhalb von __init__:
class Product: _next_id = 1Ein Attribut, das in der Klasse steht, ist ein Klassenattribut. Es gibt eines, egal wie viele Objekte du erzeugst, und alle Objekte teilen es sich. In __init__ erreichst du es über den Namen der Klasse: Product._next_id.
class Product: _next_id = 1
def __init__(self, name): self._name = name self._id = Product._next_id Product._next_id += 1
def __str__(self): return f"Nr. {self._id} {self._name}"
bread = Product("Brot")milk = Product("Milch")coffee = Product("Kaffee")print(bread)print(milk)print(coffee)Nr. 1 BrotNr. 2 MilchNr. 3 KaffeeDas Brot nimmt die 1 und macht den Zähler zu 2. Die Milch sieht denselben Zähler, nimmt die 2 und macht ihn zu 3.
class Product: _next_id = 1
def __init__(self, name): self._name = name self._id = Product._next_id Product._next_id += 1
bread = Product("Brot")milk = Product("Milch")coffee = Product("Kaffee")Schritt für Schritt, in Worten
- _next_id ist ein Klassenattribut: Es gibt eines, nicht eines in jedem Objekt.
- Product("Brot"): ein neues Objekt. __init__ speichert den Namen.
- self._id = Product._next_id: Das Objekt nimmt die aktuelle Nummer, 1.
- Product._next_id += 1 ändert den gemeinsamen Zähler: Jetzt ist er 2.
- Milch: derselbe Zähler. Es nimmt die 2 und macht ihn zu 3.
- Kaffee: Es nimmt die 3 und macht ihn zu 4.
- Es gibt einen Zähler für alle, deshalb sind die Nummern der Reihe nach.
Genauer, für Neugierige
Vorsicht mit self. Beim Lesen findet self._next_id das Klassenattribut, aber self._next_id += 1 legt ein neues Attribut auf dem Objekt an, und der gemeinsame Zähler bewegt sich nicht:
class Product: _next_id = 1
def __init__(self, name): self._name = name self._id = self._next_id self._next_id += 1
def __str__(self): return f"Nr. {self._id} {self._name}"
bread = Product("Brot")milk = Product("Milch")print(bread)print(milk)print(Product._next_id)Nr. 1 BrotNr. 1 Milch1Deshalb änderst du den gemeinsamen Wert nur über den Namen der Klasse: Product._next_id += 1.
Probier es aus
Gemeinsam und eigen
Was gibt das Programm aus?
class Order: count = 0
def __init__(self): Order.count += 1 self.items = 0 self.items += 1
a = Order()b = Order()c = Order()print(Order.count, c.items)count ist ein Klassenattribut: ein Zähler für alle Bestellungen, dreimal erhöht. items ist ein Attribut des Objekts: Jede Bestellung hat ihr eigenes, einmal erhöht.Die Idee
@staticmethod: Arbeit ohne Objekt
Dasselbe gilt für Methoden. Eine Methode mit @staticmethod darüber ist eine Methode der Klasse. Sie hat kein self, und du rufst sie über den Namen der Klasse auf, ohne ein Objekt zu erzeugen:
class Calculator: @staticmethod def add(a, b): return a + b
@staticmethod def average(values): total = 0 for v in values: total += v return total / len(values)
print(Calculator.add(10, 20))grades = [5, 6, 4, 6]print(Calculator.average(grades))print(max(3, 8))305.258Calculator.add braucht kein Objekt: Alles, was sie braucht, kommt als Parameter. In Python ist so eine Arbeit oft einfach eine Funktion außerhalb der Klasse, wie max und math.sqrt. In eine Klasse kommt sie, wenn sie eng zur Klasse gehört und du sie unter dem Namen der Klasse haben willst.
Python hat kein main in einer Klasse, bei dem das Programm startet: Das Programm sind die Zeilen der Datei, von oben nach unten. Deshalb brauchten die Funktionen aus dem Modul „Funktionen“ keine Klasse.
Genauer, für Neugierige
Es gibt auch @classmethod. Sie bekommt die Klasse selbst, cls, als ersten Parameter und erreicht die Klassenattribute ohne Objekt:
class Product: _next_id = 1
def __init__(self, name): self._name = name self._id = Product._next_id Product._next_id += 1
@classmethod def created(cls): return cls._next_id - 1
bread = Product("Brot")milk = Product("Milch")print(Product.created())2@staticmethod bekommt nichts, @classmethod bekommt die Klasse, und eine gewöhnliche Methode bekommt das Objekt (self).
Probier es aus
Finde den Fehler
Wähle die Zeile mit dem Fehler.
label() liest self._name, und der Name gehört zu einem konkreten Produkt. Eine @staticmethod bekommt kein self, also weiß sie nicht, wessen Namen sie nehmen soll:NameError: name 'self' is not defined@staticmethod und füge self als Parameter hinzu: Die Beschriftung gehört zum Produkt, also ist sie eine Instanzmethode.Die Idee
Konstanten
Die MwSt. ist für alle Produkte 20%, und sie ändert sich nicht, während das Programm läuft. So ein Wert ist eine Konstante:
class Prices: VAT_RATE = 0.20
@staticmethod def with_vat(price): return price * (1 + Prices.VAT_RATE)
print(Prices.with_vat(50))print(f"MwSt.: {Prices.VAT_RATE * 100}%")60.0MwSt.: 20.0%Python hat keine Konstanten. Es gibt eine Abmachung: Ein Name in Großbuchstaben mit Unterstrichen, VAT_RATE, bedeutet „das ändert sich nicht“. In der Klasse geschrieben, ist es ein Klassenattribut.
Wenn jemand versucht, sie zu ändern,
Prices.VAT_RATE = 0.25schützt sie nichts, und Prices.VAT_RATE ist jetzt:
0.25Die Abmachung bringt dir, dass die Zahl 0.20 an einer Stelle mit einem klaren Namen steht. Die Großbuchstaben sagen jedem, der den Code liest, „nicht anfassen“, aber den Wert zu schützen, liegt bei dir.
Genauer, für Neugierige
Final aus dem Modul typing sieht aus wie eine Konstante, aber Python prüft es nicht. Nur der Editor oder ein Werkzeug wie mypy warnt dich:
from typing import Final
class Prices: VAT_RATE: Final = 0.20
Prices.VAT_RATE = 0.25print(Prices.VAT_RATE)0.25Manchmal ist der Wert für jedes Objekt anders, darf sich aber nach dem Erzeugen des Objekts nicht ändern. So ist die Nummer des Produkts. Mach sie zu einer schreibgeschützten Property, @property ohne Setter (die Lektion „Kapselung“):
class Product: _next_id = 1
def __init__(self, name): self._name = name self._id = Product._next_id Product._next_id += 1
@property def id(self): return self._idcoffee.id liest die Nummer, und coffee.id = 5 bricht mit einem AttributeError ab, weil die Property keinen Setter hat.
Probier es aus
Was fehlt?
class Calendar:
= 12MONTHS: Die Großbuchstaben sagen, dass das eine Konstante ist. Python schützt sie nicht, aber jeder, der den Code liest, weiß, dass er sie nicht anfassen soll.Die Idee
Wann an der Klasse und wann am Objekt
Frag dich: „Gehört das zu einem konkreten Objekt?“
- Ja (der Kontostand des Kontos, der Name des Produkts, das Abheben von einem Konto): ein Attribut des Objekts in
__init__oder eine Methode mitself. - Nein (die Anzahl aller Konten, die MwSt., die Summe zweier Zahlen): ein Klassenattribut,
@staticmethododer@classmethod.
Ein häufiger Fehler ist eine Liste als Klassenattribut. Es gibt eine Liste für alle Objekte, also taucht das, was du in einen Warenkorb legst, im anderen auf:
class Cart: items = []
def add(self, name): self.items.append(name)
first = Cart()second = Cart()first.add("Brot")print(second.items)['Brot']Die Liste gehört zu jedem Warenkorb, also gehört sie in __init__: self.items = []. Sonst teilen sich alle „Objekte“ dieselben Daten, und du bist wieder beim Problem mit den parallelen Listen aus der ersten Lektion.
Übung
Was alle Objekte teilen
Löse die Aufgaben in deinem Editor.
1. Bestellungen. Eine Klasse Order mit Kunde und Betrag. Jede neue Bestellung bekommt die nächste Nummer aus einem Zähler, der ein Klassenattribut ist. Eine Methode count() mit @classmethod gibt zurück, wie viele Bestellungen erzeugt wurden.
orders = [ Order("Maria", 45.5), Order("Peter", 120), Order("Elena", 18.9),]for o in orders: print(o)print(f"Bestellungen: {Order.count()}")Bestellung Nr. 1: Maria, 45.5 €Bestellung Nr. 2: Peter, 120 €Bestellung Nr. 3: Elena, 18.9 €Bestellungen: 32. Taschenrechner. Eine Klasse Calculator mit Methoden mit @staticmethod zum Addieren, Subtrahieren und Multiplizieren ganzer Zahlen und zum Dividieren von Dezimalzahlen.
print(Calculator.add(10, 20))print(Calculator.subtract(10, 15))print(Calculator.multiply(6, 7))print(Calculator.divide(5, 2))30-5422.53. Wechselstube. Eine Klasse Converter mit Konstanten für den Kurs: 1 € = 1,08 US-Dollar und 1 € = 0,85 britische Pfund (die Kurse sind Beispiele). Methoden mit @staticmethod rechnen einen Betrag in Euro in Dollar und in Pfund um. Für 20 €:
20 EUR = 21.6 USD20 EUR = 17.0 GBPWeitere Aufgaben
- Nummerierte Konten. Gib dem
Accountaus der Lektion „Kapselung“ eine Kontonummer aus einem Zähler, der ein Klassenattribut ist, und eine schreibgeschützte Property für die Nummer. - Umrechnen in beide Richtungen. Gib
ConverterMethoden, die von Dollar und von Pfund in Euro umrechnen. Rechne 20 € in Dollar um und zurück in Euro. Bekommst du genau 20? - Ein Limit. Der Shop nimmt höchstens 3 Produkte in einem Warenkorb an. Füge dafür eine Konstante hinzu und lass die Methode zum Hinzufügen das vierte ablehnen.
Kurz gesagt
Das Modul in kurz
- Eine Klasse ist dein eigener Typ, und ein Objekt ist ein konkretes Exemplar davon, erzeugt mit
Product(). Die Variable zeigt auf das Objekt. Klassen und Objekte __init__sorgt dafür, dass das Objekt vollständig entsteht.selfist das aktuelle Objekt. Konstruktoren und self- Instanzmethoden arbeiten mit den Attributen des Objekts.
__str__sagt, wie es als Text aussieht. Instanzmethoden _namesagt „intern“, und@propertymit einem Setter lässt Änderungen erst nach einer Prüfung durch. Kapselung- Klassenattribute,
@staticmethodund@classmethodsind das, was die Klasse teilt: Zähler, Hilfsmethoden und Konstanten.
Daten und die Regeln dafür an einer Stelle, mit einer klaren Markierung, was intern ist: Das ist objektorientierte Programmierung. Im nächsten Modul fangen Klassen an, Code miteinander zu teilen.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Klassen und Objekte · Konstruktoren und self · Instanzmethoden · Kapselung
- Geht weiter in: OOP-Prinzipien · Werfen und eigene Exceptions · Reguläre Ausdrücke: Gruppen und Ersetzen · Daten in Dateien
- Aufgaben zum Thema: Klassen und Objekte