Instanzmethoden
Kurz gesagt: Eine Instanzmethode ist eine Funktion in der Klasse, deren erster Parameter self ist und die mit den Daten dieses Objekts arbeitet, und __str__ sagt, wie sich das Objekt selbst ausgibt.
Problem
Jeder hebt auf seine Art ab
Die Bank hat eine Klasse Account mit Inhaber und Kontostand. An zwei Stellen im Programm wird Geld abgehoben. Der Geldautomat prüft, ob genug Geld da ist. Die Kartenzahlung hat jemand anderes geschrieben, und sie prüft nicht:
account = Account("Maria", 100)
# Der Geldautomat prüft, ob genug Geld da ist.if account.balance >= 70: account.balance -= 70
# Die Kartenzahlung hat jemand anderes geschrieben, und eine Prüfung gibt es nicht.account.balance -= 50
print(account.balance)-20Das Konto ist im Minus. Die Regel „Du kannst nicht mehr abheben, als du hast“ ist wichtig, aber sie steht (oder fehlt) an jeder Stelle, die abhebt, einzeln.
Die Idee
Methoden wohnen bei den Daten
Die Logik des Abhebens gehört zum Konto, also kommt sie in die Klasse Account. Eine Funktion, die in der Klasse steht, ist eine Methode des Objekts:
class Account: def __init__(self, owner, balance): self.owner = owner self.balance = balance
def deposit(self, amount): self.balance += amount
def withdraw(self, amount): if amount <= self.balance: self.balance -= amount else: print(f"Nicht genug Geld: {self.owner}")
account = Account("Maria", 100)account.withdraw(70)account.withdraw(50)account.deposit(20)print(account.balance)Nicht genug Geld: Maria50- Die Methode ist ein
def, eingerückt in der Klasse. Ihr erster Parameter istself, wie bei__init__. - Du rufst sie mit einem Punkt auf einem Objekt auf:
account.withdraw(70), genau wie die Felder. Python setztaccountselbst inselfein, also übergibst du nur den Betrag. - Innen ist
self.balanceder Kontostand dieses Kontos, des Objekts vor dem Punkt. Dasself.ist Pflicht: Ohne es istbalancenicht das Feld. - Die Prüfung steht einmal da. Wer über die Methode abhebt, geht durch sie hindurch.
maria = Account("Maria", 100)peter = Account("Peter", 40)maria.withdraw(70)peter.withdraw(70)print(maria.balance, peter.balance)Schritt für Schritt, in Worten
- Zwei Konten, zwei Objekte. Jedes hat seinen eigenen Kontostand.
- maria.withdraw(70): self bekommt einen Pfeil auf das Objekt vor dem Punkt.
- In der Methode ist self.balance der Kontostand von Maria: 100 - 70 = 30.
- Dieselbe Methode, ein anderes Objekt: Jetzt zeigt self auf Peter.
- 70 ist mehr als 40: Die Methode gibt eine Ablehnung aus, und der Kontostand von Peter bleibt gleich.
- Den Code der Methode gibt es einmal. Die Felder gehören dem Objekt, auf dem du sie aufrufst.
Die Kartenzahlung kann die Prüfung nicht mehr „vergessen“, wenn sie mit withdraw abhebt. Sie kann aber immer noch direkt ins Feld schreiben. Wie du das verhinderst, siehst du in der Lektion „Kapselung“.
Die Idee
Methode oder Funktion
Die Funktionen aus dem Modul „Funktionen“ stehen außerhalb der Klasse. Sie bekommen alles als Parameter und haben kein eigenes Objekt. Eine Methode arbeitet mit den Feldern des Objekts, auf dem sie aufgerufen wurde:
def apply_discount(price, percent): return price * (100 - percent) / 100
price = apply_discount(10, 10)account.withdraw(70)Eine Instanzmethode kannst du nicht über den Namen der Klasse aufrufen. Es ist nicht klar, von welchem Konto abgehoben werden soll:
Account.withdraw(70)TypeError: Account.withdraw() missing 1 required positional argument: 'amount'70 ist in self gelandet, und für amount blieb nichts übrig. Tatsächlich ist account.withdraw(70) die Kurzform von Account.withdraw(account, 70): Das Objekt vor dem Punkt wird zum ersten Argument. Es gibt auch Methoden der Klasse selbst, ohne Objekt. Du siehst sie in der Lektion „Was alle Objekte teilen: Klassenattribute“.
Probier es aus
Wessen Konto?
Was gibt das Programm aus?
maria = Account("Maria", 100)peter = Account("Peter", 40)maria.deposit(30)peter.withdraw(10)print(maria.balance, peter.balance)Jedes Objekt hat seinen eigenen Kontostand. maria.deposit(30) ändert nur den Kontostand von Maria, und peter.withdraw(10) nur den von Peter. Peter hat 40, also klappt das Abheben von 10.
Probier es aus
Finde den Fehler
account.withdraw(30) hält das Programm mit einem Fehler an. In welcher Zeile ist der Fehler?Wähle die Zeile mit dem Fehler.
Ohne self. ist der Name balance eine gewöhnliche Variable, und die Methode hat keine solche Variable:
NameError: name 'balance' is not definedDas Feld ist self.balance. Die nächste Zeile schreibt es richtig, der Fehler steckt also nur in der Prüfung.
Die Idee
Eine Methode kann auch antworten
Eine Instanzmethode kann ein Ergebnis zurückgeben, genau wie die Funktionen aus dem Modul „Funktionen“. Ein Produkt kennt seinen Preis und seine Stückzahl, also kann es dir seine eigenen Kosten sagen:
class Product: # die Felder und der Konstruktor, wie vorher
def total(self): return self.price * self.quantity
cart = [ Product("Brot", 1.20, 2), Product("Milch", 2.10, 1), Product("Kaffee", 7.50, 1),]amount = 0for p in cart: amount += p.total()print(f"Gesamt: {amount} €")Gesamt: 12.0 €Vergleich das mit der ersten Lektion des Moduls: Dort wurde die Summe in dem Code unter der Klasse aus den Feldern berechnet. Jetzt fragt dieser Code nur noch jedes Produkt. Kommt morgen ein Mengenrabatt dazu, änderst du nur total().
Die Idee
Wie sieht das Objekt als Text aus?
Wenn du ein Produkt direkt ausgibst, bekommst du etwas Nutzloses:
coffee = Product("Kaffee", 7.50, 1)print(coffee)<__main__.Product object at 0x7f3a2c1b2d10>Python gibt den Namen der Klasse und die Adresse des Objekts im Speicher aus, die bei jedem Start anders ist. Über das Produkt sagt das nichts.
Die Klasse kann sagen, wie sie angezeigt werden soll. Die Methode heißt __str__ und gibt Text zurück:
class Product: # die Felder und der Konstruktor, wie vorher
def total(self): return self.price * self.quantity
def __str__(self): return f"{self.name} x{self.quantity}: {self.total()} €"
coffee = Product("Kaffee", 7.50, 1)bread = Product("Brot", 1.20, 2)print(coffee)print(f"Im Warenkorb: {bread}")Kaffee x1: 7.5 €Im Warenkorb: Brot x2: 2.4 €Du rufst sie nicht selbst auf. print(coffee) und der f-String f"Im Warenkorb: {bread}" rufen sie für dich auf, wenn das Objekt zu Text werden muss. Die zwei Unterstriche vorn und hinten bedeuten genau das: Die Methode ist für Python, und Python ruft sie im richtigen Moment von selbst auf, wie __init__ beim Erzeugen.
Mit + kannst du aber nur Text an Text hängen. "Im Warenkorb: " + bread bricht ab mit
TypeError: can only concatenate str (not "Product") to strNimm einen f-String oder "Im Warenkorb: " + str(bread): Auch str() ruft __str__ auf.
Genauer, für Neugierige
Neben __str__ gibt es auch __repr__: den Text für den Programmierer. Eine Liste benutzt ihn, wenn du die Liste ausgibst, deshalb liefert print(cart) ohne __repr__ immer noch <__main__.Product object at …>. Nach der Abmachung sieht __repr__ aus wie der Code, der das Objekt erzeugt:
class Product: # die Felder und der Konstruktor, wie vorher
def __repr__(self): return f'Product("{self.name}", {self.price}, {self.quantity})'
cart = [Product("Kaffee", 7.50, 1), Product("Brot", 1.20, 2)]print(cart)[Product("Kaffee", 7.5, 1), Product("Brot", 1.2, 2)]Probier es aus
Ein Buch in einem Satz
Was gibt das Programm aus?
class Book: def __init__(self, title, author): self.title = title self.author = author
def __str__(self): return f"{self.title} von {self.author}"
book = Book("Der Zauberberg", "Thomas Mann")print(f"Ich lese: {book}")f"Ich lese: {book}" muss book zu Text machen. Also wird __str__ des Buches aufgerufen, und es gibt Titel und Autor zurück.Übung
Objekte, die etwas tun
Löse die Aufgaben in deinem Editor. Nimm die Klassen aus den früheren Lektionen und gib ihnen Methoden.
Person: eine Methodeintroduce(), die Name und Alter ausgibt.SimpleCar: eine Methodeaccelerate(), die die Geschwindigkeit um 10 erhöht. Jetzt fasst der Code unter der Klasse die Geschwindigkeit nicht mehr direkt an.DanceMove: eine Methodeperform(), die den Schritt „ausführt“.Book: eine Methoderead(), die „Ich lese gerade [Titel] von [Autor]“ ausgibt.
Erzeuge von jeder Klasse ein Objekt und ruf seine Methoden auf:
Hallo, ich bin Maria und bin 34 Jahre alt.Geschwindigkeit nach dem Beschleunigen: 10Ich tanze moonwalk für 5 SekundenIch lese gerade Der Prozess von Franz KafkaÜbung
Ein Auto mit Tank
Eine Klasse Car mit Marke, Geschwindigkeit, Kraftstoff (in Litern) und Kilometerstand. Der Konstruktor bekommt Marke und Kraftstoff. Geschwindigkeit und Kilometerstand beginnen bei 0.
accelerate()erhöht die Geschwindigkeit um 10.refuel(liters)tankt nach, aber in den Tank passen höchstens 50 Liter.drive(distance)verbraucht 6 Liter auf 100 km und addiert die Strecke zum Kilometerstand. Reicht der Kraftstoff nicht, gibt die Methode eine Meldung aus, und das Auto fährt nicht.__str__()gibt Marke, Kilometerstand und Kraftstoff zurück (den Kraftstoff mit einer Stelle nach dem Punkt:{self.fuel:.1f}).
car = Car("Toyota", 20)car.accelerate()car.accelerate()print(f"Geschwindigkeit: {car.speed}")car.drive(100)print(car)car.drive(300)car.refuel(40)print(car)car.drive(300)print(car)Geschwindigkeit: 20Toyota: 100 km, 14.0 lNicht genug Kraftstoff für 300 kmToyota: 100 km, 50.0 lToyota: 400 km, 32.0 ldistance * 6 / 100. In Python gibt / immer eine Dezimalzahl, also bekommst du für 30 km 1,8 Liter.Weitere Aufgaben
- Rabatt. Gib
Producteine Methodeapply_discount(percent), die den Preis des Produkts senkt. Vergleich sie mit der Funktion aus dem Modul „Funktionen“, die einen neuen Preis zurückgegeben hat. - Ein Konto mit Verlauf. Gib
Accounteinen Zähler für Vorgänge: Jede Einzahlung und jede erfolgreiche Abhebung erhöht ihn um 1. Eine Methode__str__()gibt Inhaber, Kontostand und Anzahl der Vorgänge zurück.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Funktionen · Parameter und return · Klassen und Objekte · Konstruktoren und self
- Geht weiter in: Kapselung · Was alle Objekte teilen: Klassenattribute · Vererbung · Polymorphie
- Aufgaben zum Thema: Klassen und Objekte