701 Stimmen

Warum wird __init__() immer nach __new__() aufgerufen?

Ich versuche gerade, eine meiner Klassen zu rationalisieren und habe einige Funktionen im gleichen Stil wie die Fliegengewicht-Designmuster .

Allerdings bin ich ein wenig verwirrt, warum __init__ wird immer aufgerufen nach __new__ . Das habe ich nicht erwartet. Kann mir jemand sagen, warum das passiert und wie ich diese Funktion anders implementieren kann? (Abgesehen davon, dass ich die Implementierung in die __new__ was sich ziemlich hakelig anfühlt.)

Hier ist ein Beispiel:

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

Ausgänge:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Warum?

1 Stimmen

Versuchte, Design Patterns zu verstehen, und hörte zum ersten Mal von :flyweight Design Patterns.. und sehr guter Link mit Beispielen in fast allen gängigen Sprachen.

5 Stimmen

Ist es nicht ein Singleton?

5voto

kiriloff Punkte 24291

Bezug nehmend auf dieses Dokument :

Bei der Unterklassifizierung von unveränderlichen eingebauten Typen wie Zahlen und Zeichenketten, und gelegentlich in anderen Situationen, wird die statische Methode __new__ kommt nützlich sein. __new__ ist der erste Schritt in der Instanzkonstruktion, der aufgerufen wird vor __init__ .

En __new__ Methode wird mit der Klasse als aufgerufen; ihre Aufgabe ist es, eine neue Instanz dieser Klasse zurückzugeben.

Vergleichen Sie dies mit __init__ : __init__ wird mit einer Instanz aufgerufen als erstes Argument aufgerufen und gibt nichts zurück; seine Aufgabe ist es, die Instanz zu initialisieren.

Es gibt Situationen in denen eine neue Instanz erzeugt wird, ohne dass der Aufruf __init__ (zum Beispiel wenn die Instanz aus einem Pickle geladen wird). Es gibt keine Möglichkeit, eine eine neue Instanz ohne den Aufruf von __new__ (obwohl Sie in einigen Fällen mit dem Aufruf einer Basisklasse von __new__ ).

In Bezug auf das, was Sie erreichen möchten, gibt es auch im gleichen Dokument Informationen über Singleton Muster

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

können Sie auch diese Implementierung aus PEP 318 verwenden, indem Sie einen Dekorator

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

5voto

xpapazaf Punkte 252

Das muss man sich mal genauer ansehen!

Der Typ einer generischen Klasse in CPython ist type und seine Basisklasse ist Object (Es sei denn, Sie definieren ausdrücklich eine andere Basisklasse wie eine Metaklasse). Die Abfolge der Aufrufe auf niedriger Ebene ist wie folgt ici . Die erste aufgerufene Methode ist die type_call der dann aufruft tp_new und dann tp_init .

Das Interessante daran ist, dass tp_new ruft die Object (Basisklasse) neue Methode object_new die eine tp_alloc ( PyType_GenericAlloc ), die den Speicher für das Objekt zuweist :)

Zu diesem Zeitpunkt wird das Objekt im Speicher erstellt und dann die __init__ Methode aufgerufen wird. Wenn __init__ in Ihrer Klasse nicht implementiert ist, wird die object_init aufgerufen wird und nichts bewirkt :)

Dann type_call gibt einfach das Objekt zurück, das an Ihre Variable gebunden ist.

5voto

grdvnl Punkte 628

Man sollte sich ansehen __init__ wie ein einfacher Konstruktor in traditionellen OO-Sprachen. Wenn Sie zum Beispiel mit Java oder C++ vertraut sind, wird dem Konstruktor implizit ein Zeiger auf seine eigene Instanz übergeben. Im Fall von Java ist es der this variabel. Wenn man sich den für Java generierten Bytecode ansieht, fallen einem zwei Aufrufe auf. Der erste Aufruf erfolgt über eine "new"-Methode, der nächste über die "init"-Methode (die den eigentlichen Aufruf des benutzerdefinierten Konstruktors darstellt). Dieser zweistufige Prozess ermöglicht die Erstellung der eigentlichen Instanz vor dem Aufruf der Konstruktormethode der Klasse, die nur eine weitere Methode dieser Instanz ist.

Nun, im Fall von Python, __new__ ist eine hinzugefügte Einrichtung, die für den Benutzer zugänglich ist. Java bietet diese Flexibilität nicht, da es typisiert ist. Wenn eine Sprache diese Möglichkeit bieten würde, dann müsste der Implementierer von __new__ könnte in dieser Methode viele Dinge tun, bevor sie die Instanz zurückgibt, einschließlich der Erstellung einer völlig neuen Instanz eines nicht verwandten Objekts in einigen Fällen. Und dieser Ansatz funktioniert auch gut, insbesondere für unveränderliche Typen in Python.

5voto

Andrew Wilkinson Punkte 10354

__new__ sollte eine neue, leere Instanz einer Klasse zurückgeben. __init__ wird dann aufgerufen, um diese Instanz zu initialisieren. Du rufst __init__ im "NEW"-Fall von __new__ nicht auf, also wird es für dich aufgerufen. Der Code, der __init__ aufruft __new__ merkt sich nicht, ob __init__ für eine bestimmte Instanz aufgerufen wurde oder nicht, und das sollte es auch nicht, denn Sie machen hier etwas sehr Ungewöhnliches.

Sie könnten dem Objekt in der Funktion __init__ ein Attribut hinzufügen, um anzuzeigen, dass es initialisiert wurde. Prüfen Sie das Vorhandensein dieses Attributs als erstes in __init__ und fahren Sie nicht weiter fort, wenn es vorhanden ist.

3voto

Jetzt habe ich das gleiche Problem, und aus einigen Gründen habe ich beschlossen, Dekoratoren, Fabriken und Metaklassen zu vermeiden. Ich habe es so gemacht:

Hauptakte

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init

class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

Beispielklassen

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)

class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

Im Einsatz

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • Warnung: Sie sollten Parent nicht initialisieren, es wird mit anderen Klassen kollidieren - es sei denn, Sie haben für jedes Kind einen eigenen Cache definiert, was wir nicht wollen.
  • Warnung: Es scheint, dass sich eine Klasse mit Parent als Großelternteil seltsam verhält. [Unverifiziert]

Probieren Sie es online aus!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X