Spezielle Methoden in Python-Klassen erkunden

PythonBeginner
Jetzt üben

Einführung

In diesem Lab werden Sie einige der speziellen Methoden von Python erkunden, die oft als „Dunder“-Methoden bezeichnet werden, aufgrund ihrer Namen mit doppelten Unterstrichen. Sie werden ein praktisches Verständnis dafür entwickeln, wie diese Methoden es Ihnen ermöglichen, das Verhalten Ihrer Klassen und Objekte anzupassen.

Sie lernen die Methode __new__ zur Steuerung der Instanzerstellung und die Methode __del__ zur Objektzerstörung kennen. Sie werden auch sehen, wie Sie __slots__ verwenden können, um die Speichernutzung zu optimieren und Attribute einzuschränken, sowie wie Sie Ihre Klasseninstanzen mit der Methode __call__ wie Funktionen aufrufbar machen. Anhand von praktischen Beispielen lernen Sie, effizienteren und ausdrucksstärkeren Python-Code zu schreiben.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 100% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Die __new__-Methode verstehen und verwenden

In diesem Schritt werden Sie die Methode __new__ erkunden. Während __init__ üblicherweise verwendet wird, um die Attribute eines Objekts nach dessen Erstellung zu initialisieren, ist __new__ die Methode, die die Instanz tatsächlich erzeugt. Sie wird vor __init__ aufgerufen.

Hier sind die Hauptunterschiede:

  • __new__ ist eine statische Methode, die die Klasse (cls) als erstes Argument entgegennimmt. Sie ist dafür verantwortlich, eine neue Instanz der Klasse zu erstellen und zurückzugeben.
  • __init__ ist eine Instanzmethode, die die Instanz (self) als erstes Argument entgegennimmt. Sie initialisiert das neu erstellte Objekt und gibt nichts zurück.

Normalerweise müssen Sie __new__ nicht überschreiben, da die Standardimplementierung der object-Klasse ausreichend ist. Sie ist jedoch für fortgeschrittene Fälle nützlich, wie die Implementierung des Singleton-Musters oder die Erstellung von Instanzen unveränderlicher (immutable) Typen.

Sehen wir uns __new__ in Aktion an. Sie werden eine Dog-Klasse erstellen, die eine Nachricht während der Instanzerstellung ausgibt.

Öffnen Sie zuerst die Datei dog_cat.py im Dateiexplorer auf der linken Seite der IDE.

Fügen Sie den folgenden Code in die Datei dog_cat.py ein. Dieser Code definiert eine Basisklasse Animal und eine Unterklasse Dog, die die Methode __new__ überschreibt.

## File Name: dog_cat.py

class Animal:
    def __init__(self, name):
        self._name = name
        print(f'Initializing {self._name} in Animal.')

    def say(self):
        print(self._name + ' is saying something')

class Dog(Animal):
    ## Der erste Parameter ist cls, der sich auf die Klasse selbst bezieht.
    ## Er muss auch alle Argumente akzeptieren, die an den Konstruktor übergeben werden.
    def __new__(cls, name, age):
        print('A new Dog instance is being created.')
        ## Rufen Sie die __new__-Methode der Elternklasse auf, um die Instanz zu erstellen.
        instance = super().__new__(cls)
        return instance

    def __init__(self, name, age):
        print(f'Initializing {name} in Dog.')
        ## Das __init__ der Elternklasse wird aufgerufen, um den Namen festzulegen.
        super().__init__(name)
        self.age = age

    def say(self):
        print(self._name + ' is making a sound: wang wang wang...')

## Erstellen einer Dog-Instanz
print("Creating a Dog object...")
d = Dog('Buddy', 5)
print("Dog object created.")
print(f"Dog's name: {d._name}, Age: {d.age}")

Speichern Sie die Datei (Strg+S oder Cmd+S).

Öffnen Sie nun ein Terminal in Ihrer IDE (Sie können das Menü Terminal > New Terminal verwenden). Führen Sie das Skript aus, um die Reihenfolge der Methodenaufrufe zu beobachten.

python ~/project/dog_cat.py

Sie werden die folgende Ausgabe sehen. Beachten Sie, dass __new__ zuerst aufgerufen wird, um die Instanz zu erstellen, gefolgt von den __init__-Methoden, um sie zu initialisieren.

Creating a Dog object...
A new Dog instance is being created.
Initializing Buddy in Dog.
Initializing Buddy in Animal.
Dog object created.
Dog's name: Buddy, Age: 5

Dies demonstriert, dass __new__ die Erstellung des Objekts steuert und __init__ es anschließend konfiguriert.

Die __del__-Methode implementieren und testen

In diesem Schritt lernen Sie die Methode __del__ kennen. Diese Methode wird als Finalizer oder Destruktor bezeichnet. Sie wird aufgerufen, wenn die Referenzanzahl eines Objekts auf null sinkt, was bedeutet, dass es kurz davorsteht, vom Garbage Collector von Python zerstört zu werden. Sie wird häufig für Aufräumarbeiten verwendet, wie das Schließen von Netzwerkverbindungen oder Dateihandles.

Sie können eine Referenz auf ein Objekt mithilfe der del-Anweisung entfernen. Wenn die letzte Referenz verschwunden ist, wird __del__ automatisch aufgerufen.

Fügen wir unserer Dog-Klasse eine __del__-Methode hinzu, um zu sehen, wann ein Objekt zerstört wird.

Öffnen Sie erneut die Datei dog_cat.py. Ersetzen Sie den gesamten Inhalt der Datei durch den folgenden Code. Diese Version entfernt den Code, der eine Dog-Instanz erstellt (um zu vermeiden, dass sie beim Importieren des Moduls erstellt wird), und fügt der Dog-Klasse die Methode __del__ hinzu.

## File Name: dog_cat.py

class Animal:
    def __init__(self, name):
        self._name = name

    def say(self):
        print(self._name + ' is saying something')

class Dog(Animal):
    def __new__(cls, name, age):
        instance = super().__new__(cls)
        return instance

    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

    def say(self):
        print(self._name + ' is making a sound: wang wang wang...')

    ## Fügen Sie diese Methode zur Dog-Klasse hinzu
    def __del__(self):
        print(f'The Dog object {self._name} is being deleted.')

Speichern Sie die Datei dog_cat.py.

Erstellen wir nun ein separates Skript, um dieses Verhalten zu testen. Öffnen Sie die Datei test_del.py im Dateiexplorer.

Fügen Sie den folgenden Code in test_del.py ein. Dieses Skript erstellt zwei Dog-Instanzen und löscht dann eine davon explizit.

## File Name: test_del.py

from dog_cat import Dog
import time

print("Creating two Dog objects: d1 and d2.")
d1 = Dog('Tom', 3)
d2 = Dog('John', 5)

print("\nDeleting reference to d1...")
del d1
print("Reference to d1 deleted.")

## Der Garbage Collector wird möglicherweise nicht sofort ausgeführt.
## Wir fügen eine kleine Verzögerung hinzu, um ihm Zeit zur Ausführung zu geben.
time.sleep(1)

print("\nScript is about to end. d2 will be deleted automatically.")

Speichern Sie die Datei. Führen Sie nun das Skript test_del.py im Terminal aus.

python ~/project/test_del.py

Beobachten Sie die Ausgabe. Die __del__-Meldung für Tom erscheint, nachdem del d1 aufgerufen wurde. Die Meldung für John erscheint ganz am Ende, da das Objekt d2 automatisch vom Garbage Collector bereinigt wird, wenn das Skript endet.

Creating two Dog objects: d1 and d2.

Deleting reference to d1...
The Dog object Tom is being deleted.
Reference to d1 deleted.

Script is about to end. d2 will be deleted automatically.
The Dog object John is being deleted.

Hinweis: Der genaue Zeitpunkt der Garbage Collection kann variieren. __del__ wird aufgerufen, wenn das Objekt gesammelt wird, nicht unbedingt unmittelbar nachdem del verwendet wurde.

Attribute mit __slots__ kontrollieren

In diesem Schritt lernen Sie __slots__ kennen. Standardmäßig speichert Python Instanzattribute in einem speziellen Wörterbuch (Dictionary) namens __dict__. Dies ermöglicht es Ihnen, einem Objekt jederzeit neue Attribute hinzuzufügen. Diese Flexibilität verbraucht jedoch zusätzlichen Speicher.

Durch die Definition eines __slots__-Attributs in Ihrer Klasse können Sie eine feste Liste von Attributen festlegen, die Instanzen besitzen dürfen. Dies hat zwei Haupteffekte:

  1. Speichereinsparungen: Python verwendet eine kompaktere interne Struktur anstelle eines __dict__ für jede Instanz, was die Speichernutzung erheblich reduzieren kann, insbesondere beim Erstellen vieler Objekte.
  2. Attributbeschränkung: Sie können einem Instanzobjekt keine Attribute mehr hinzufügen, die nicht in __slots__ aufgeführt sind. Dies hilft, Tippfehler zu vermeiden und eine strikte Objektstruktur zu erzwingen.

Erstellen wir ein Beispiel, um zu sehen, wie __slots__ funktioniert. Öffnen Sie die Datei slots_example.py im Dateiexplorer.

Fügen Sie den folgenden Code in slots_example.py ein:

## File Name: slots_example.py

class Player:
    ## Definieren Sie die erlaubten Attribute mithilfe von __slots__
    __slots__ = ('name', 'level')

    def __init__(self, name, level):
        self.name = name
        self.level = level

## Erstellen einer Instanz von Player
p1 = Player('Hero', 10)

## Zugriff auf die erlaubten Attribute
print(f"Player name: {p1.name}")
print(f"Player level: {p1.level}")

## Nun versuchen, ein neues Attribut hinzuzufügen, das NICHT in __slots__ enthalten ist
print("\nTrying to add a 'score' attribute...")
try:
    p1.score = 100
    print(f"Player score: {p1.score}")
except AttributeError as e:
    print(f"Caught an error: {e}")

## Überprüfen Sie auch, ob die Instanz ein __dict__-Attribut besitzt
print("\nChecking for __dict__...")
try:
    print(p1.__dict__)
except AttributeError as e:
    print(f"Caught an error: {e}")

Speichern Sie die Datei. Führen Sie nun das Skript slots_example.py im Terminal aus.

python ~/project/slots_example.py

Die Ausgabe zeigt, dass Sie Werten für name und level zuweisen können, aber der Versuch, score einen Wert zuzuweisen, löst einen AttributeError aus. Es wird auch bestätigt, dass die Instanz kein __dict__ besitzt.

Player name: Hero
Player level: 10

Trying to add a 'score' attribute...
Caught an error: 'Player' object has no attribute 'score'

Checking for __dict__...
Caught an error: 'Player' object has no attribute '__dict__'

Dies demonstriert, wie __slots__ einen festen Satz von Attributen erzwingen und den Speicher optimieren kann, indem das Instanzwörterbuch eliminiert wird.

Instanzen mit __call__ aufrufbar machen

In diesem Schritt werden Sie die Methode __call__ untersuchen. In Python werden Objekte, die mit Klammern () wie Funktionen "aufgerufen" werden können, als aufrufbare Objekte (callable objects) bezeichnet. Funktionen und Methoden sind von Natur aus aufrufbar.

Standardmäßig sind Klasseninstanzen nicht aufrufbar. Wenn Sie jedoch die spezielle Methode __call__ in einer Klasse definieren, werden deren Instanzen aufrufbar. Wenn Sie eine Instanz wie eine Funktion aufrufen, wird der Code in ihrer __call__-Methode ausgeführt. Dies ist nützlich, um Objekte zu erstellen, die sich wie Funktionen verhalten, aber auch ihren eigenen internen Zustand beibehalten können.

Erstellen wir eine Klasse, deren Instanzen aufgerufen werden können. Öffnen Sie die Datei callable_instance.py im Dateiexplorer.

Fügen Sie den folgenden Code in callable_instance.py ein:

## File Name: callable_instance.py

class Greeter:
    def __init__(self, greeting):
        ## Dieser Zustand wird mit der Instanz gespeichert
        self.greeting = greeting
        print(f'Greeter initialized with "{self.greeting}"')

    ## Definieren Sie die __call__-Methode, um Instanzen aufrufbar zu machen
    def __call__(self, name):
        ## Dieser Code wird ausgeführt, wenn die Instanz aufgerufen wird
        print(f"{self.greeting}, {name}!")

## Erstellen einer Instanz von Greeter
hello_greeter = Greeter("Hello")

## Überprüfen, ob die Instanz mit der eingebauten Funktion callable() aufrufbar ist
print(f"Is hello_greeter callable? {callable(hello_greeter)}")

## Nun die Instanz aufrufen, als wäre sie eine Funktion
print("\nCalling the instance:")
hello_greeter("Alice")
hello_greeter("Bob")

## Eine weitere Instanz mit einem anderen Zustand erstellen
goodbye_greeter = Greeter("Goodbye")
print("\nCalling the second instance:")
goodbye_greeter("Charlie")

Speichern Sie die Datei. Führen Sie nun das Skript callable_instance.py im Terminal aus.

python ~/project/callable_instance.py

Die Ausgabe zeigt, dass die Instanz hello_greeter tatsächlich aufrufbar ist. Jedes Mal, wenn Sie sie aufrufen, wird die Methode __call__ ausgeführt, wobei der Zustand (self.greeting) verwendet wird, der bei der Initialisierung festgelegt wurde.

Greeter initialized with "Hello"
Is hello_greeter callable? True

Calling the instance:
Hello, Alice!
Hello, Bob!
Greeter initialized with "Goodbye"

Calling the second instance:
Goodbye, Charlie!

Dies demonstriert, wie __call__ es Ihnen ermöglicht, zustandsbehaftete, funktionsähnliche Objekte zu erstellen, was eine mächtige Funktion in der objektorientierten Programmierung ist.

Zusammenfassung

In diesem Lab haben Sie mehrere mächtige spezielle Methoden (special methods) in Python kennengelernt. Sie haben gelernt, wie Sie __new__ verwenden, um den Prozess der Instanzerstellung zu steuern, was Ihnen einen Eingriffspunkt (hook) gibt, bevor __init__ aufgerufen wird. Sie haben die Methode __del__ implementiert, um Bereinigungslogik zu definieren, die ausgeführt wird, wenn ein Objekt vom Garbage Collector freigegeben wird. Außerdem haben Sie __slots__ verwendet, um Speicher zu optimieren und ein striktes Attributmodell zu erzwingen, indem die Erstellung eines Instanz-__dict__ verhindert wird. Schließlich haben Sie Ihre Objekte dazu gebracht, sich wie Funktionen zu verhalten, indem Sie die Methode __call__ implementiert haben. Durch die Beherrschung dieser Dunder-Methoden können Sie flexiblere, effizientere und Pythonischere Klassen schreiben.