Sicherstellen, dass __init__ nur einmal aufgerufen wird, wenn die Klasseninstanz vom Konstruktor erstellt wird oder __new__

8

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.

%Vor%

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

neu definieren könnte %Vor%

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.

    
Abiel 26.12.2011, 07:58
quelle

3 Antworten

8

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.
  • By __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:

%Vor%

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__ :

definieren %Vor%     
unutbu 26.12.2011, 08:34
quelle
1

Es gibt ein paar Dinge, die nicht gemacht werden sollten:

  • Aufruf __init__ von __new__
  • Rufen Sie __new__ direkt in einer Methode
  • auf

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:

%Vor%     
jcollado 26.12.2011 08:13
quelle
0

Wenn Sie eine Instanz einer Klasse mit MyClass(args) erstellen, lautet die standardmäßige Instanzerstellungssequenz wie folgt:

  1. MyClass.__new__(args) wird aufgerufen, um eine neue "leere" Instanz zu erhalten
  2. 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]
  3. zu initialisieren
  4. 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:

  1. Sie haben ein ungewöhnliches "Zuweisungs" -Schema, bei dem Sie möglicherweise eine vorhandene Instanz zurückgeben und keine wirklich neue erstellen (die einzige Möglichkeit, eine neue Instanz zu erstellen, besteht darin, an die ultimative Standardimplementierung von __new__ zu delegieren. sowieso).
  2. Sie implementieren eine Unterklasse eines unveränderlichen Built-in-Typs. Da die unveränderlichen Built-In-Typen nach der Erstellung nicht geändert werden können (weil sie unveränderlich sind), müssen sie als initialisiert werden und nicht später in __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.

    
Ben 03.01.2012 00:49
quelle

Tags und Links