Python (und Python C API): __new__ versus __init__

126

Die Frage, die ich stellen werde, scheint ein Duplikat von Pythons Verwendung von __new__ und __init__ zu sein. Trotzdem ist mir immer noch unklar, was genau der praktische Unterschied zwischen __new__und __init__ist.

Bevor Sie mir sagen, dass dies __new__zum Erstellen von Objekten und __init__zum Initialisieren von Objekten dient, lassen Sie mich klar sein: Ich verstehe das. Tatsächlich ist diese Unterscheidung für mich ganz natürlich, da ich Erfahrung in C ++ habe, wo wir eine neue Platzierung haben , die in ähnlicher Weise die Objektzuweisung von der Initialisierung trennt.

Das Python C API-Tutorial erklärt es folgendermaßen:

Das neue Mitglied ist für das Erstellen (im Gegensatz zum Initialisieren) von Objekten des Typs verantwortlich. Es wird in Python als __new__()Methode verfügbar gemacht. ... Ein Grund für die Implementierung einer neuen Methode besteht darin, die Anfangswerte der Instanzvariablen sicherzustellen .

Also, ja - ich verstehe , was es __new__tut, aber trotzdem verstehe ich immer noch nicht, warum es in Python nützlich ist. Das angegebene Beispiel besagt, dass __new__dies nützlich sein kann, wenn Sie "die Anfangswerte von Instanzvariablen sicherstellen" möchten. Ist das nicht genau das, was __init__wir tun werden?

Im C API-Tutorial wird ein Beispiel gezeigt, in dem ein neuer Typ ("Noddy" genannt) erstellt und die __new__Funktion des Typs definiert wird. Der Noddy-Typ enthält ein Zeichenfolgenelement mit dem Namen first, und dieses Zeichenfolgenelement wird wie folgt mit einer leeren Zeichenfolge initialisiert:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    .....

    self->first = PyString_FromString("");
    if (self->first == NULL)
    {
       Py_DECREF(self);
       return NULL;
    }

    .....
}

Beachten Sie, dass __new__wir ohne die hier definierte Methode verwenden müssten PyType_GenericNew, die einfach alle Instanzvariablenelemente auf NULL initialisiert. Der einzige Vorteil der __new__Methode besteht darin, dass die Instanzvariable im Gegensatz zu NULL als leere Zeichenfolge beginnt. Aber warum ist das jemals nützlich, denn wenn wir sicherstellen wollten, dass unsere Instanzvariablen auf einen Standardwert initialisiert werden, hätten wir das einfach in der __init__Methode tun können ?

Kanal72
quelle

Antworten:

137

Der Unterschied ergibt sich hauptsächlich bei veränderlichen und unveränderlichen Typen.

__new__akzeptiert einen Typ als erstes Argument und gibt (normalerweise) eine neue Instanz dieses Typs zurück. Somit ist es sowohl für veränderliche als auch für unveränderliche Typen geeignet.

__init__akzeptiert eine Instanz als erstes Argument und ändert die Attribute dieser Instanz. Dies ist für einen unveränderlichen Typ ungeeignet, da sie nach der Erstellung durch Aufrufen geändert werden könnenobj.__init__(*args) .

Vergleichen Sie das Verhalten von tupleund list:

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

Warum sie getrennt sind (abgesehen von einfachen historischen Gründen): __new__Methoden erfordern eine Reihe von Boilerplates, um richtig zu sein (die anfängliche Objekterstellung und dann daran zu denken, das Objekt am Ende zurückzugeben). __init__Im Gegensatz dazu sind Methoden kinderleicht, da Sie nur die Attribute festlegen, die Sie festlegen müssen.

Abgesehen davon, dass __init__Methoden einfacher zu schreiben sind und die oben erwähnte Unterscheidung zwischen veränderlichen und unveränderlichen Methoden, kann die Trennung auch ausgenutzt werden, um das Aufrufen der übergeordneten Klasse __init__in Unterklassen optional zu machen, indem unbedingt erforderliche Instanzinvarianten in eingerichtet werden __new__. Dies ist jedoch im Allgemeinen eine zweifelhafte Praxis - es ist normalerweise klarer, die übergeordneten Klassenmethoden __init__nach Bedarf aufzurufen .

ncoghlan
quelle
1
Der Code, den Sie als "Boilerplate" bezeichnen, __new__ist kein Boilerplate, da sich das Boilerplate nie ändert. Manchmal müssen Sie diesen bestimmten Code durch etwas anderes ersetzen.
Miles Rout
13
Das Erstellen oder anderweitige Abrufen der Instanz (normalerweise mit einem superAufruf) und das Zurückgeben der Instanz sind notwendige Bestandteile jeder __new__Implementierung und des "Boilerplate", auf das ich mich beziehe. Im Gegensatz dazu passist eine gültige Implementierung für __init__- es ist überhaupt kein Verhalten erforderlich.
Ncoghlan
37

Es gibt wahrscheinlich andere Verwendungszwecke, __new__aber es gibt einen wirklich offensichtlichen: Sie können einen unveränderlichen Typ nicht ohne Verwendung unterordnen __new__. Angenommen, Sie möchten eine Unterklasse von Tupeln erstellen, die nur ganzzahlige Werte zwischen 0 und 0 enthalten kann size.

class ModularTuple(tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

Sie können einfach tun dies nicht mit __init__- wenn man versucht , zu ändern , selfin __init__der Interpreter würde, beschweren sich, dass Sie ein unveränderliches Objekt zu ändern versuchen.

senderle
quelle
1
Ich verstehe nicht, warum wir super verwenden sollen? Ich meine, warum sollte new eine Instanz der Superklasse zurückgeben? Darüber hinaus, wie Sie es ausdrückten, warum sollten wir cls explizit an new übergeben ? super (ModularTuple, cls) gibt keine gebundene Methode zurück?
Alcott
3
@Alcott, ich denke du missverstehst das Verhalten von __new__. Wir gehen clsexplizit zu, __new__weil, wie Sie hier lesen können , __new__ immer ein Typ als erstes Argument erforderlich ist. Anschließend wird eine Instanz dieses Typs zurückgegeben. Wir geben also keine Instanz der Oberklasse zurück - wir geben eine Instanz von zurück cls. In diesem Fall ist es genauso, als hätten wir gesagt tuple.__new__(ModularTuple, tup).
senderle
35

__new__()kann Objekte anderer Typen als der Klasse zurückgeben, an die es gebunden ist. __init__()Initialisiert nur eine vorhandene Instanz der Klasse.

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
Ignacio Vazquez-Abrams
quelle
Dies ist die bisher schlankste Erklärung.
Tarik
Nicht ganz richtig. Ich habe __init__Methoden, die Code enthalten, der aussieht self.__class__ = type(...). Dies führt dazu, dass das Objekt einer anderen Klasse angehört als die, von der Sie dachten, dass Sie sie erstellt haben. Ich kann es nicht so ändern intwie Sie ... Ich erhalte eine Fehlermeldung über Heap-Typen oder ähnliches ... aber mein Beispiel für die Zuweisung zu einer dynamisch erstellten Klasse funktioniert.
ArtOfWarfare
Auch ich bin verwirrt darüber, wann __init__()angerufen wird. In der Antwort von lonetwin wird beispielsweise je nach zurückgegebenem Typ entweder Triangle.__init__()oder Square.__init__()automatisch aufgerufen __new__(). Nach dem, was Sie in Ihrer Antwort sagen (und ich habe dies an anderer Stelle gelesen), scheint es nicht so, als ob einer von beiden sein sollte, da Shape.__new__() keine Instanz von cls(oder eine von einer Unterklasse davon) zurückgegeben wird.
Martineau
1
@martineau: Die __init__() Methoden in lonetwins Antwort werden aufgerufen, wenn die einzelnen Objekte instanziiert werden (dh wenn ihre __new__() Methode zurückgegeben wird), nicht, wenn sie Shape.__new__()zurückgegeben werden.
Ignacio Vazquez-Abrams
Ahh, richtig Shape.__init__()(wenn es einen hätte) würde man nicht anrufen. Jetzt macht alles mehr Sinn ...:¬)
Martineau
13

Keine vollständige Antwort, aber vielleicht etwas, das den Unterschied verdeutlicht.

__new__wird immer aufgerufen, wenn ein Objekt erstellt werden muss. Es gibt Situationen, in denen __init__nicht angerufen wird. Ein Beispiel ist, wenn Sie Objekte aus einer Pickle-Datei entfernen, werden sie zugewiesen ( __new__), aber nicht initialisiert ( __init__).

Noufal Ibrahim
quelle
Würde ich init von new aus aufrufen, wenn Speicher zugewiesen und Daten initialisiert werden sollen? Warum, wenn beim Erstellen der Instanz init keine neue vorhanden ist ?
redpix_
2
Die Aufgabe der __new__Methode besteht darin , eine Instanz der Klasse zu erstellen (dies impliziert eine Speicherzuweisung) und diese zurückzugeben. Die Initialisierung ist ein separater Schritt und für den Benutzer normalerweise sichtbar. Bitte stellen Sie eine separate Frage, wenn Sie auf ein bestimmtes Problem stoßen.
Noufal Ibrahim
3

Ich möchte nur ein Wort über die hinzufügen Absicht (im Gegensatz zum Verhalten) , __new__Versus zu definieren __init__.

Ich bin (unter anderem) auf diese Frage gestoßen, als ich versuchte zu verstehen, wie man eine Klassenfabrik am besten definiert. Ich erkannte, dass einer der Wege, auf denen__new__ sich konzeptionell von der unterscheidet, darin __init__besteht, dass der Nutzen __new__genau das ist, was in der Frage angegeben wurde:

Der einzige Vorteil der __new__- Methode besteht darin, dass die Instanzvariable im Gegensatz zu NULL als leere Zeichenfolge beginnt. Aber warum ist dies jemals nützlich, denn wenn wir sicherstellen wollten, dass unsere Instanzvariablen auf einen Standardwert initialisiert werden, hätten wir dies einfach mit der Methode __init__ tun können?

In Anbetracht des angegebenen Szenarios kümmern wir uns um die Anfangswerte der Instanzvariablen, wenn die Instanz in Wirklichkeit eine Klasse selbst ist. Wenn wir also zur Laufzeit dynamisch ein Klassenobjekt erstellen und etwas Besonderes für die nachfolgenden Instanzen dieser Klasse definieren / steuern müssen, definieren wir diese Bedingungen / Eigenschaften in einer __new__Methode einer Metaklasse.

Ich war darüber verwirrt, bis ich tatsächlich über die Anwendung des Konzepts nachdachte und nicht nur über die Bedeutung. Hier ist ein Beispiel, das hoffentlich den Unterschied deutlich machen würde:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

Beachten Sie, dass dies nur ein dämonstartives Beispiel ist. Es gibt mehrere Möglichkeiten, eine Lösung zu erhalten, ohne auf einen Klassenfactory-Ansatz wie oben zurückzugreifen, und selbst wenn wir uns dafür entscheiden, die Lösung auf diese Weise zu implementieren, werden der Kürze halber einige Einschränkungen ausgelassen (z. B. explizite Deklaration der Metaklasse) )

Wenn Sie eine reguläre Klasse (auch als Nicht-Metaklasse bezeichnet) erstellen, ist __new__dies nur dann sinnvoll, wenn es sich um einen Sonderfall wie das veränderbare oder unveränderliche Szenario in der Antwort von ncoghlan handelt (was im Wesentlichen ein spezifischeres Beispiel für das Konzept der Definition ist Die Anfangswerte / Eigenschaften der Klasse / des Typs, über die erstellt wird __new__, werden dann über initialisiert __init__.

lonetwin
quelle