Ich versuche zu verstehen, wie neue Instanzen einer Python-Klasse erstellt werden sollen, wenn der Erstellungsprozess entweder über den Konstruktor oder über die __new__
-Methode erfolgen kann. Insbesondere bemerke ich, dass bei Verwendung des Konstruktors die Methode __init__
automatisch nach __new__
aufgerufen wird, während beim Aufruf von __new__
direkt die Klasse __init__
nicht automatisch aufgerufen wird. Ich kann erzwingen, dass __init__
aufgerufen wird, wenn __new__
explizit aufgerufen wird, indem ein Aufruf in __init__
in __new__
eingebettet wird, aber dann wird __init__
zweimal aufgerufen, wenn die Klasse über den Konstruktor erstellt wird.
Betrachten Sie zum Beispiel die folgende Spielzeugklasse, die eine interne Eigenschaft speichert, nämlich ein list
-Objekt mit dem Namen data
: Es ist nützlich, sich dies als Beginn einer Vektorklasse vorzustellen.
Eine neue Instanz der Klasse kann entweder mit dem Konstruktor erstellt werden (nicht wirklich sicher, ob das die richtige Terminologie in Python ist), etwa wie
x = MyClass(range(10))
oder über das Slicing, das Sie sehen können, ruft einen Aufruf von __new__
in der Methode __getitem__
auf.
x2 = x[0:2]
Zuerst wird __init__
zweimal aufgerufen (sowohl über den expliziten Aufruf innerhalb von __new__
als auch wieder automatisch), und einmal in der zweiten Instanz. Offensichtlich möchte ich nur __init__
auf jeden Fall einmal aufrufen. Gibt es eine Standardmethode, dies in Python zu tun?
Beachten Sie, dass ich in meinem Beispiel die Methode __new__
loswerden und __getitem__
als
Aber das würde dann ein Problem verursachen, wenn ich später von MyClass
erben möchte, denn wenn ich einen Aufruf wie child_instance[0:2]
mache, bekomme ich eine Instanz von MyClass
zurück, nicht die Kindklasse.
Zunächst einige grundlegende Fakten zu __new__
und __init__
:
__new__
ist ein Konstruktor . __new__
gibt normalerweise eine Instanz von cls
, dem ersten Argument, zurück. __new__
gibt eine Instanz von cls
zurück, __new__
bewirkt, dass Python% co_de aufruft % . __init__
ist ein Initialisierer . Es ändert die Instanz ( __init__
)
zurückgegeben von self
. Es muss nicht __new__
zurückgegeben werden. Wenn self
definiert:
MyClass
wird zweimal aufgerufen. Einmal vom Aufruf von MyClass.__init__
explizit, und ein zweites Mal, weil obj.__init__
__new__
zurückgegeben hat, eine Instanz von obj
. (Da das erste Argument für cls
object.__new__
ist, ist die zurückgegebene Instanz eine Instanz von cls
, also MyClass
ruft obj.__init__
, nicht MyClass.__init__
auf.)
Die Python 2.2.3-Versionshinweise haben einen interessanten Kommentar, der sich abzeichnet leuchtet auf, wann object.__init__
verwendet werden soll und wann __new__
verwendet werden soll:
Die Methode
__init__
wird mit der Klasse als erstem Argument aufgerufen. es ist Aufgabe ist es, eine neue Instanz dieser Klasse zurückzugeben.Vergleichen Sie dies mit
__new__
:__init__
wird mit einer Instanz als dessen aufgerufen erstes Argument, und es gibt nichts zurück; seine Verantwortung ist um die Instanz zu initialisieren.All dies wird getan, damit unveränderliche Arten ihre bewahren können Unveränderlichkeit, während Unterklassenbildung erlaubt.
Die unveränderlichen Typen (int, long, float, complex, str, Unicode und Tupel) haben einen Dummy
__init__
, während die veränderbaren Typen (dict, list, Datei, und auch super, classmethod, staticmethod und property) haben a dummy__init__
.
Verwenden Sie also __new__
, um unveränderliche Typen zu definieren, und verwenden Sie __new__
, um änderbare Typen zu definieren. Obwohl es möglich ist, beides zu definieren, sollten Sie dies nicht tun müssen.
Da MyClass also änderbar ist, sollten Sie nur __init__
:
Es gibt ein paar Dinge, die nicht gemacht werden sollten:
__init__
von __new__
__new__
direkt in einer Methode Wie Sie bereits gesehen haben, werden die Methoden __new__
und __init__
automatisch aufgerufen, wenn ein Objekt einer bestimmten Klasse erstellt wird. Wenn Sie sie direkt verwenden, wird diese Funktionalität zerstört (das Aufrufen von __init__
in einem anderen __init__
ist jedoch zulässig, wie im folgenden Beispiel zu sehen ist).
Sie können die Klasse des Objekts in einer beliebigen Methode abrufen, indem Sie das __class__
-Attribut wie im folgenden Beispiel erhalten:
Wenn Sie eine Instanz einer Klasse mit MyClass(args)
erstellen, lautet die standardmäßige Instanzerstellungssequenz wie folgt:
MyClass.__new__(args)
wird aufgerufen, um eine neue "leere" Instanz zu erhalten new_instance.__init__(args)
wird aufgerufen ( new_instance
ist die Instanz, die vom Aufruf von __new__
wie oben zurückgegeben wurde), um die Attribute der neuen Instanz [1] new_instance
wird als Ergebnis von MyClass(args)
zurückgegeben
Daraus wird deutlich, dass das Aufrufen von MyClass.__new__
yourself nicht dazu führt, dass __init__
aufgerufen wird, sodass Sie eine nicht initialisierte Instanz erhalten. Es ist ebenso klar, dass ein Aufruf von __init__
in __new__
ebenfalls nicht korrekt ist, da dann MyClass(args)
__init__
zweimal aufruft.
Die Quelle Ihres Problems ist dies:
Ich versuche zu verstehen, wie neue Instanzen einer Python-Klasse sein sollten erstellt, wenn der Erstellungsprozess entweder über den Konstruktor oder über die neue Methode
Der Erstellungsprozess sollte normalerweise nicht über die Methode __new__
erfolgen. __new__
ist ein Teil des normalen Instanz-Erstellungsprotokolls. Sie sollten also nicht erwarten, dass es das gesamte Protokoll für Sie aufruft.
Eine (schlechte) Lösung wäre, dieses Protokoll von Hand selbst zu implementieren; statt:
%Vor%könnte man haben:
%Vor% Aber wirklich, was Sie tun wollen, ist nicht mit __new__
zu tun. Der Standardwert __new__
ist für Ihren Fall in Ordnung, und das standardmäßige Instanzerstellungsprotokoll ist für Sie in Ordnung. Sie sollten also weder __new__
implementieren noch direkt aufrufen.
Sie möchten eine neue Instanz der Klasse auf normale Weise erstellen, indem Sie die Klasse aufrufen. Wenn keine Vererbung stattfindet und Sie nicht glauben, dass dies jemals der Fall sein wird, ersetzen Sie einfach self.__new__(type(self), self.data[index])
durch MyClass(self.data[index])
.
Wenn Sie der Meinung sind, dass eines Tages Unterklassen von MyClass
vorhanden sein könnten, die Instanzen der Unterklasse durch das Slicen und nicht durch MyClass
erstellen möchten, müssen Sie die Klasse self
dynamisch abrufen und diese Klasse aufrufen. Sie wissen bereits, wie Sie das tun, weil Sie es in Ihrem Programm verwendet haben! type(self)
gibt den Typ (Klasse) von self
zurück, den Sie dann genau so aufrufen können, wie Sie ihn direkt über MyClass
: type(self)(self.data[index])
aufrufen würden.
Nebenbei, der Punkt von __new__
ist, wenn Sie den Prozess anpassen wollen, eine "neue" leere Instanz einer Klasse zu bekommen, bevor sie initialisiert wird. Fast die ganze Zeit ist dies völlig unnötig und der Standard __new__
ist in Ordnung.
Sie benötigen __new__
nur in zwei Fällen:
__new__
zu delegieren. sowieso). __init__
. Als eine Verallgemeinerung von Punkt (1) können Sie __new__
zurückgeben, was immer Sie wollen (nicht unbedingt eine Instanz der Klasse), um den Aufruf einer Klasse auf irgendeine willkürlich bizarre Weise zu veranlassen. Dies scheint jedoch fast immer verwirrender als hilfreich zu sein.
[1] Ich glaube, dass das Protokoll etwas komplexer ist; __init__
wird nur für den von __new__
zurückgegebenen Wert aufgerufen, wenn es sich um eine Instanz der Klasse handelt, die zum Starten des Prozesses aufgerufen wurde. Es ist jedoch sehr ungewöhnlich, dass dies nicht der Fall ist.
Tags und Links python