Spezielle Methoden in Python-Klassen erkunden

PythonPythonBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einleitung

In diesem Lab tauchen Sie in die faszinierende Welt der speziellen Methoden von Python ein, die aufgrund ihrer doppelten Unterstriche oft als "Dunder"-Methoden bezeichnet werden. Sie werden ein praktisches Verständnis dafür entwickeln, wie diese Methoden das Verhalten Ihrer Klassen und Objekte beeinflussen.

Insbesondere werden Sie die __new__-Methode untersuchen, die die Instanzerstellung steuert, und die __del__-Methode, die für die Objektzerstörung zuständig ist. Sie lernen auch, wie Sie den Speicherverbrauch optimieren und die Attributerstellung mit __slots__ einschränken können, und entdecken, wie Sie Ihre Klasseninstanzen wie Funktionen aufrufbar machen können, indem Sie die __call__-Methode verwenden. Anhand praktischer Beispiele sehen Sie, wie diese speziellen Methoden Sie befähigen, robustere, effizientere und ausdrucksstärkere Python-Codes zu erstellen.

Die __new__-Methode verstehen und verwenden

In diesem Schritt werden wir die __new__()-Methode in Python-Klassen untersuchen. Während __init__() üblicherweise zur Initialisierung von Objektattributen nach der Erstellung verwendet wird, ist __new__() die Methode, die bevor die Instanz erstellt wird, aufgerufen wird. Sie ist für die Erstellung und Rückgabe des neuen Instanzobjekts verantwortlich.

Die Hauptunterschiede zwischen __new__() und __init__() sind:

  • __init__() wird nachdem die Instanz erstellt wurde, aufgerufen. Ihr erstes Argument ist self, das sich auf die Instanz selbst bezieht. Sie muss nichts zurückgeben.
  • __new__() wird bevor die Instanz erstellt wird, aufgerufen. Ihr erstes Argument ist cls, das sich auf die Klasse selbst bezieht. Sie muss das neue Instanzobjekt zurückgeben.

Meistens müssen Sie __new__() nicht definieren, da die Standardimplementierung, die von der object-Klasse geerbt wird, die Instanzerstellung übernimmt. Wenn Sie jedoch den Instanzerstellungsprozess anpassen müssen (z. B. zur Implementierung von Singletons oder unveränderlichen Objekten), können Sie __new__() überschreiben.

Beim Überschreiben von __new__() rufen Sie typischerweise die __new__()-Methode der Elternklasse mit super().__new__(cls) auf und geben das Ergebnis zurück. Alle an den Klassenkonstruktor übergebenen Argumente (außer cls) werden dann an die __init__()-Methode weitergegeben.

Erstellen wir ein einfaches Beispiel, um __new__() in Aktion zu sehen. Wir erstellen eine Klasse Dog, die von Animal erbt, und fügen eine __new__()-Methode hinzu, um eine Nachricht auszugeben, wenn eine neue Dog-Instanz erstellt wird.

Öffnen Sie die Datei dog_cat.py im VS Code-Editor. Wenn die Datei nicht existiert, erstellen Sie sie im Verzeichnis ~/project.

code ~/project/dog_cat.py

Fügen Sie den folgenden Code zur Datei dog_cat.py hinzu:

## File Name: dog_cat.py

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

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

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

class Dog(Animal):
    ## Note that the first parameter of this method is cls, which points to the class itself
    ## The parameters used in the __init__ method must be included, except for self
    def __new__(cls, name, age):
        print('A new dog is being created.')
        ## Use the super method to call the parent class's method with the same name
        return super().__new__(cls)
        ## The line below has the same effect as the line above, both return the result of calling the parent class method
        #return object.__new__(cls)

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

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

Speichern Sie die Datei.

Öffnen Sie nun ein neues Terminal in VS Code und führen Sie dieses Skript mit dem Befehl python aus, um die Ausgabe der __new__-Methode zu sehen.

python ~/project/dog_cat.py

Sie werden noch keine Ausgabe sehen, da wir noch keine Instanz der Dog-Klasse erstellt haben. Fügen wir Code hinzu, um eine Dog-Instanz zu erstellen und sie auszugeben.

Ändern Sie die Datei dog_cat.py und fügen Sie am Ende die folgenden Zeilen hinzu:

## File Name: dog_cat.py

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

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

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

class Dog(Animal):
    ## Note that the first parameter of this method is cls, which points to the class itself
    ## The parameters used in the __init__ method must be included, except for self
    def __new__(cls, name, age):
        print('A new dog is being created.')
        ## Use the super method to call the parent class's method with the same name
        return super().__new__(cls)
        ## The line below has the same effect as the line above, both return the result of calling the parent class method
        #return object.__new__(cls)

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

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

## Create a Dog instance
d = Dog('Tom', 11)
print(d)

Speichern Sie die Datei und führen Sie sie erneut im Terminal aus:

python ~/project/dog_cat.py

Sie sollten die Ausgabe der __new__-Methode sehen, bevor die Instanz ausgegeben wird.

A new dog is being created.
<__main__.Dog object at 0x...>

Dies zeigt, dass die __new__-Methode vor der __init__-Methode aufgerufen wird und für die Erstellung des Instanzobjekts verantwortlich ist.

Implementierung und Test der __del__-Methode

In diesem Schritt lernen wir die __del__()-Methode kennen, die das Gegenstück zu __new__() und __init__() ist. Die __del__()-Methode ist eine spezielle Methode, die aufgerufen wird, wenn ein Objekt kurz vor der Garbage Collection steht (d. h. wenn keine Referenzen mehr auf das Objekt vorhanden sind). Sie wird oft für Bereinigungsoperationen verwendet, wie z. B. das Schließen von Dateien oder die Freigabe von Ressourcen.

Sie können eine Objekt-Referenz explizit mit dem Schlüsselwort del löschen. Wenn die letzte Referenz auf ein Objekt gelöscht wird und das Objekt vom Garbage Collector eingesammelt wird, wird die __del__()-Methode (falls definiert) aufgerufen.

Modifizieren wir unsere Dog-Klasse in dog_cat.py, um eine __del__()-Methode hinzuzufügen, die eine Nachricht ausgibt, wenn eine Dog-Instanz gelöscht wird.

Öffnen Sie die Datei dog_cat.py im VS Code-Editor:

code ~/project/dog_cat.py

Fügen Sie die folgende __del__-Methode zur Dog-Klasse hinzu:

## File Name: dog_cat.py

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

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

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

class Dog(Animal):
    def __new__(cls, name, age):
        print('A new dog is being created.')
        return super().__new__(cls)

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

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

    ## Add this method to the Dog class
    def __del__(self):
        print(f'Deleting {self._name} instance.')

## Create a Dog instance
d = Dog('Tom', 11)
print(d)

Speichern Sie die Datei.

Erstellen wir nun ein neues Python-Skript, um die __del__-Methode zu testen. Erstellen Sie eine neue Datei namens test_del.py im Verzeichnis ~/project:

code ~/project/test_del.py

Fügen Sie den folgenden Code zu test_del.py hinzu:

## File Name: test_del.py

from dog_cat import Dog

## Create two Dog instances
d1 = Dog('Tom', 3)
d2 = Dog('John', 5)

## Delete the first instance
print("Deleting d1...")
del d1

## Try to access d1 (will raise an error)
## print(d1) ## Uncommenting this line will cause an error

## Call __del__ on the second instance explicitly (does not delete the object)
print("Calling __del__ on d2...")
d2.__del__()

## d2 still exists
print("d2 still exists:")
print(d2)

## The __del__ method for d2 will be called when the script finishes and d2 is garbage collected.

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. Sie sollten die Nachricht von __new__ sehen, wenn die Instanzen erstellt werden, die Nachricht von __del__, wenn d1 gelöscht wird, und die Nachricht von __del__, wenn d2.__del__() explizit aufgerufen wird. Der letzte __del__-Aufruf für d2 erfolgt automatisch, wenn das Skript beendet wird und d2 vom Garbage Collector eingesammelt wird.

A new dog is being created.
A new dog is being created.
Deleting d1...
Calling __del__ on d2...
Deleting John instance.
d2 still exists:
<dog_cat.Dog object at 0x...>
Deleting John instance.

Beachten Sie, dass der explizite Aufruf von d2.__del__() den Code der Methode ausführt, das Objekt aber nicht tatsächlich löscht. Das Objekt wird erst vom Garbage Collector eingesammelt, wenn keine Referenzen mehr darauf zeigen.

Attribute mit __slots__ kontrollieren

In diesem Schritt lernen wir das __slots__-Attribut kennen. Standardmäßig speichern Python-Instanzen ihre Attribute in einem Wörterbuch (__dict__). Dies ermöglicht es Ihnen, zur Laufzeit dynamisch neue Attribute zu einer Instanz hinzuzufügen. Diese Flexibilität geht jedoch mit einem Speicheraufwand einher, da jede Instanz ihr eigenes Wörterbuch benötigt.

Das __slots__-Attribut ermöglicht es Ihnen, explizit die Attribute zu deklarieren, die eine Instanz einer Klasse haben wird. Wenn __slots__ definiert ist, erstellt Python kein Instanzwörterbuch (__dict__) für die Instanzen dieser Klasse. Stattdessen verwendet es eine speichereffizientere Struktur, um die in __slots__ aufgeführten Attribute zu speichern. Dies kann für Klassen mit einer großen Anzahl von Instanzen von Vorteil sein, da es den Speicherverbrauch erheblich reduzieren kann.

Eine weitere Auswirkung der Verwendung von __slots__ ist, dass es die Attribute einschränkt, die zu einer Instanz hinzugefügt werden können. Sie können nur Attribute hinzufügen, die im __slots__-Tupel aufgeführt sind. Der Versuch, ein anderes Attribut hinzuzufügen, führt zu einem AttributeError.

Erstellen wir ein neues Python-Skript, um die Verwendung von __slots__ zu demonstrieren. Erstellen Sie eine Datei namens slots_example.py im Verzeichnis ~/project:

code ~/project/slots_example.py

Fügen Sie den folgenden Code zu slots_example.py hinzu:

## File Name: slots_example.py

class Animal:
    ## Define the allowed attributes using __slots__
    __slots__ = ('name', 'age')

## Create an instance of Animal
a = Animal()

## Assign values to the allowed attributes
a.name = 'Tom'
a.age = 10

## Print the values
print(f"Animal name: {a.name}")
print(f"Animal age: {a.age}")

## Try to add an attribute not in __slots__
try:
    a.role = 'dog'
    print(f"Animal role: {a.role}")
except AttributeError as e:
    print(f"Error: {e}")

## Check if the instance has a __dict__ attribute
try:
    print(a.__dict__)
except AttributeError as e:
    print(f"Error: {e}")

## Demonstrate inheritance with __slots__
class Cat(Animal):
    pass

cat = Cat()
cat.name = 'Kity'
cat.role = 'cat' ## This will work because Cat does not define __slots__

print(f"Cat name: {cat.name}")
print(f"Cat role: {cat.role}")

## Demonstrate __slots__ in a subclass
class Bird(Animal):
    __slots__ = ('hometown',)

bird = Bird()
bird.name = 'John'          ## Inherited from Animal's __slots__
bird.hometown = 'New York'  ## Defined in Bird's __slots__
## bird.role = 'bird'          ## This will raise an AttributeError

print(f"Bird name: {bird.name}")
print(f"Bird hometown: {bird.hometown}")

## Try to add an attribute not in Bird's or Animal's __slots__
try:
    bird.role = 'bird'
    print(f"Bird role: {bird.role}")
except AttributeError as e:
    print(f"Error: {e}")

Speichern Sie die Datei.

Führen Sie nun das Skript slots_example.py im Terminal aus:

python ~/project/slots_example.py

Beobachten Sie die Ausgabe. Sie sollten sehen, dass der Versuch, das role-Attribut zur Animal-Instanz hinzuzufügen, zu einem AttributeError führt. Sie sollten auch einen AttributeError sehen, wenn Sie versuchen, auf das __dict__-Attribut der Animal-Instanz zuzugreifen, was bestätigt, dass es nicht erstellt wurde.

Für die Cat-Klasse, die von Animal erbt, aber keine eigene __slots__-Definition hat, können Sie das role-Attribut dynamisch hinzufügen.

Für die Bird-Klasse, die ihre eigenen __slots__ definiert, können Sie Attribute hinzufügen, die sowohl in den __slots__ von Animal als auch in denen von Bird aufgeführt sind. Der Versuch, ein Attribut hinzuzufügen, das in keinem der __slots__ enthalten ist, führt zu einem AttributeError.

Animal name: Tom
Animal age: 10
Error: 'Animal' object has no attribute 'role'
Error: 'Animal' object has no attribute '__dict__'
Cat name: Kity
Cat role: cat
Bird name: John
Bird hometown: New York
Error: 'Bird' object has no attribute 'role'

Dieses Beispiel zeigt, wie __slots__ verwendet werden kann, um die zulässigen Attribute einer Instanz zu steuern und potenziell Speicher zu sparen.

Instanzen mit __call__ aufrufbar machen

In diesem Schritt werden wir die __call__()-Methode untersuchen. In Python können einige Objekte wie Funktionen "aufgerufen" werden. Diese werden als aufrufbare Objekte bezeichnet. Funktionen, Methoden und Klassen sind Beispiele für eingebaute aufrufbare Objekte. Sie können mit der integrierten Funktion callable() überprüfen, ob ein Objekt aufrufbar ist.

Standardmäßig sind Instanzen einer Klasse nicht aufrufbar. Sie können eine Instanz jedoch aufrufbar machen, indem Sie die __call__()-Methode in ihrer Klasse definieren. Wenn Sie eine Instanz wie eine Funktion aufrufen (z. B. instance()), führt Python den Code innerhalb der __call__()-Methode der Instanz aus.

Diese spezielle Methode ermöglicht es Instanzen einer Klasse, sich wie Funktionen zu verhalten, was in verschiedenen Szenarien nützlich sein kann, z. B. beim Erstellen funktionsähnlicher Objekte, die einen Zustand beibehalten, oder bei der Implementierung benutzerdefinierter Decorators.

Erstellen wir eine einfache Klasse mit einer __call__()-Methode, um zu sehen, wie sie funktioniert. Erstellen Sie eine neue Datei namens callable_instance.py im Verzeichnis ~/project:

code ~/project/callable_instance.py

Fügen Sie den folgenden Code zu callable_instance.py hinzu:

## File Name: callable_instance.py

class Greeter:
    def __init__(self, greeting):
        self.greeting = greeting

    ## Define the __call__ method to make instances callable
    def __call__(self, name):
        print(f"{self.greeting}, {name}!")

## Create an instance of Greeter
hello_greeter = Greeter("Hello")
goodbye_greeter = Greeter("Goodbye")

## Check if the instances are callable
print(f"Is hello_greeter callable? {callable(hello_greeter)}")
print(f"Is goodbye_greeter callable? {callable(goodbye_greeter)}")

## Call the instances like functions
hello_greeter("Alice")
goodbye_greeter("Bob")

## Check if the class itself is callable
print(f"Is Greeter class callable? {callable(Greeter)}")

## Check if a regular function is callable
def simple_function():
    pass

print(f"Is simple_function callable? {callable(simple_function)}")

Speichern Sie die Datei.

Führen Sie nun das Skript callable_instance.py im Terminal aus:

python ~/project/callable_instance.py

Beobachten Sie die Ausgabe. Sie sollten sehen, dass sowohl die Instanzen hello_greeter als auch goodbye_greeter aufrufbar sind und dass ihr Aufruf ihre jeweiligen __call__()-Methoden ausführt und die personalisierten Begrüßungen ausgibt. Sie werden auch sehen, dass die Klasse Greeter und die Funktion simple_function wie erwartet aufrufbar sind.

Is hello_greeter callable? True
Is goodbye_greeter callable? True
Hello, Alice!
Goodbye, Bob!
Is Greeter class callable? True
Is simple_function callable? True

Dies zeigt, wie die __call__()-Methode es Ihnen ermöglicht, Instanzen Ihrer Klassen aufrufbar zu machen, sodass sie in Kontexten verwendet werden können, in denen Funktionen erwartet werden.

Zusammenfassung

In diesem Lab haben wir mehrere spezielle Methoden in Python-Klassen untersucht, die eine erweiterte Kontrolle über Objekterstellung, Verhalten und Ressourcenverwaltung ermöglichen. Wir begannen mit dem Verständnis und der Nutzung der __new__-Methode, die für die Instanzerstellung zuständig ist, bevor __init__ aufgerufen wird, und einen Mechanismus zur Anpassung des Objektinstanziierungsprozesses bietet.

Danach implementierten und testeten wir die __del__-Methode, um zu verstehen, wie Bereinigungsaktionen definiert werden, die ausgeführt werden, wenn ein Objekt gerade vom Garbage Collector eingesammelt wird. Anschließend lernten wir, wie __slots__ verwendet wird, um die Attributerstellung zu steuern und potenziell Speicher zu sparen, indem die Erstellung eines Instanzwörterbuchs verhindert wird. Schließlich machten wir Instanzen aufrufbar, indem wir die __call__-Methode implementierten, wodurch Objekte wie Funktionen behandelt werden können.