Geschichte der Frage:
In Python ist alles ein Objekt, und jede Funktion ist ebenfalls ein Objekt. Es wurde ein Mechanismus implementiert, der es einer Klasseninstanz ermöglicht, sich wie eine Funktion zu verhalten. Dazu wurde die spezielle magische Methode call eingeführt, die es ermöglicht, Objekte aufrufbar zu machen.
Problem:
Manchmal ist es erforderlich, ein Objekt zu übergeben, das Aktionen bei einem Aufruf ausführen kann, wie z.B. zustandsbehaftete Funktionen, Closures, Ereignis-Handlerobjekte usw. Die Entwicklung der Architektur erfordert ein Verständnis dafür, wie man diese Funktionalität korrekt umsetzt.
Lösung:
Durch die Implementierung der Methode call in einer Klasse kann man deren Instanz "wie eine Funktion" aufrufbar machen. Dies ermöglicht die Kombination der Fähigkeiten von Klassen (Kapselung von Zustand, Vererbung, Methoden) und Funktionen (Aufruffähigkeit). Dieser Ansatz wird verwendet, um Kommandobjekte, komplexe Handler, Wrapper usw. zu erstellen.
Codebeispiel:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Gibt 15 aus
Schlüsselmerkmale:
Erbt die Methode call die Attribute einer gewöhnlichen Funktion – z.B. name und doc?
Nein, das Objekt mit der Methode call hat das Attribut name nicht (oder übernimmt es von der Klasse). Die Metadaten von Funktionen werden nicht beibehalten.
Ist ein Objekt mit implementiertem call eine echte Funktion?
Nein, es ist eine Klasseninstanz, keine Funktion. Es implementiert nur das "aufrufbare" Verhalten. Zum Beispiel wird der Vergleich mit einer Funktion über isinstance(obj, types.FunctionType) False ergeben.
Kann ein Dekorator, der für Funktionen gedacht ist, auf ein Objekt mit call angewendet werden?
Normalerweise erwarten solche Dekoratoren eine Funktion und kein Objekt (z.B. functools.lru_cache). Die Verwendung kann zu Fehlern führen oder ganz fehlschlagen.
Vorteile:
Negativer Fall: In einem Projekt wurde ein Logger durch eine Klasse mit call implementiert, um Einstellungen (Level, Dateiname) zu speichern. Man vergaß jedoch, dass Signal-Handler-Funktionen tatsächlich eine Funktion erwarten, und erhielt Fehler bei der Registrierung des Handlers (object is not a function).
Vorteile: flexible Konfiguration des Loggers. Nachteile: Inkompatibilität mit erwarteten Schnittstellen.
Positiver Fall: In einem anderen Projekt wurde eine Klasse mit call verwendet, um komplexe Dekoratorfunktionen zu erstellen und Parameter zu speichern, was das Testen erleichterte.
Vorteile: Erweiterbarkeit, Benutzerfreundlichkeit. Nachteile: mehr Code im Vergleich zu einer Funktion oder lambda.