Perché __init __ () viene sempre chiamato dopo __new __ ()?


568

Sto solo cercando di semplificare una delle mie lezioni e ho introdotto alcune funzionalità nello stesso stile del modello di progettazione dei pesi mosca .

Tuttavia, sono un po 'confuso sul perché __init__viene sempre chiamato dopo __new__. Non me lo aspettavo. Qualcuno può dirmi perché questo sta accadendo e come posso implementare questa funzionalità in caso contrario? (A parte mettere l'implementazione in quello __new__che sembra abbastanza confuso.)

Ecco un esempio:

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()

Uscite:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Perché?


Stavo cercando di capire anche il modello di progettazione, e la prima volta sentito parlare di: modello di progettazione dei pesi mosca ... e un ottimo collegamento che ha un esempio in quasi tutte le lingue popolari.
EmmaYang

Risposte:


598

Utilizzare __new__quando è necessario controllare la creazione di una nuova istanza.

Utilizzare __init__quando è necessario controllare l'inizializzazione di una nuova istanza.

__new__è il primo passo della creazione dell'istanza. Viene chiamato per primo ed è responsabile della restituzione di una nuova istanza della tua classe.

Al contrario, __init__non restituisce nulla; è responsabile dell'inizializzazione dell'istanza solo dopo che è stata creata.

In generale, non è necessario eseguire l'override a __new__meno che non si stia eseguendo la sottoclasse di un tipo immutabile come str, int, unicode o tuple.

Da aprile 2008 post: quando usare __new__vs. __init__? su mail.python.org.

Dovresti considerare che ciò che stai cercando di fare è di solito fatto con una fabbrica e questo è il modo migliore per farlo. L'uso __new__non è una buona soluzione pulita, quindi ti preghiamo di considerare l'uso di una fabbrica. Ecco un buon esempio di fabbrica .


1
Ho finito per usare __new__all'interno di una classe Factory, che è diventata abbastanza pulita, quindi grazie per il tuo contributo.
Dan

11
Siamo spiacenti, non sono d'accordo sul fatto che l'uso di __new__dovrebbe essere strettamente limitato ai casi indicati. L'ho trovato molto utile per l'implementazione di fabbriche di classi generiche estensibili - vedi la mia risposta alla domanda Uso improprio di __new__generare classi in Python? per un esempio di farlo.
martineau,

171

__new__è un metodo di classe statica, mentre __init__è un metodo di istanza. __new__deve prima creare l'istanza, quindi __init__può inizializzarla. Si noti che __init__accetta selfcome parametro. Fino a quando non si crea un'istanza, non esiste self.

Ora, mi rendo conto che stai cercando di implementare il modello singleton in Python. Ci sono alcuni modi per farlo.

Inoltre, a partire da Python 2.6, puoi usare i decoratori di classe .

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

@singleton
class MyClass:
  ...

8
@Tyler Long, non capisco bene come funzioni "@singleton"? Perché il decoratore restituisce una funzione ma MyClass è una classe.
Alcott,

11
Perché il dizionario? Poiché cls sarà sempre lo stesso e otterrai un nuovo dizionario, ad esempio per ogni singleton, stai creando un dizionario con solo un elemento al suo interno.
Winston Ewert,

7
@Alcott: nessuna opinione necessaria - i documenti sono d'accordo con te.
Ethan Furman,

2
@Alcott. sì, il decoratore restituisce una funzione. ma sia la classe che la funzione sono richiamabili. Penso che instance = {} dovrebbe essere una variabile globale.
Tyler Long,

4
@TylerLong Alcott ha un buon punto. Ciò comporta che il nome MyClassè associato a una funzione che restituisce istanze della classe originale. Ma ora non c'è modo di fare riferimento alla classe originale, ed MyClassessendo una funzione interruzioni isinstance/ issubclasscontrolli, accesso agli attributi / metodi MyClass.somethingdella classe direttamente come , denominazione della classe nelle superchiamate, ecc. Ecc. Che si tratti di un problema o no dipende dal classe a cui lo stai applicando (e il resto del programma).
Ben

149

Nelle lingue OO più note, un'espressione simile SomeClass(arg1, arg2)alloca una nuova istanza, inizializza gli attributi dell'istanza e quindi la restituisce.

Nei linguaggi OO più noti, la parte "inizializza gli attributi dell'istanza" può essere personalizzata per ogni classe definendo un costruttore , che è fondamentalmente solo un blocco di codice che opera sulla nuova istanza (usando gli argomenti forniti all'espressione del costruttore ) per impostare le condizioni iniziali desiderate. In Python, questo corrisponde al __init__metodo della classe .

Python __new__non è niente di più e niente di meno che una personalizzazione per classe simile della parte "alloca una nuova istanza". Questo ovviamente ti consente di fare cose insolite come restituire un'istanza esistente piuttosto che allocarne una nuova. Quindi in Python, non dovremmo davvero pensare a questa parte come necessariamente all'allocazione; tutto ciò di cui abbiamo bisogno è che __new__si presenti un'istanza adatta da qualche parte.

Ma è ancora solo metà del lavoro, e non c'è modo per il sistema Python di sapere che a volte vuoi eseguire l'altra metà del lavoro ( __init__) in seguito e a volte no. Se vuoi quel comportamento, devi dirlo esplicitamente.

Spesso, puoi fare il refactoring in modo da averne solo bisogno __new__, o così non ti serve __new__, o in modo che __init__si comporti diversamente su un oggetto già inizializzato. Ma se lo desideri davvero, Python ti consente effettivamente di ridefinire "il lavoro", in modo che SomeClass(arg1, arg2)non sia necessariamente chiamato __new__seguito da __init__. Per fare ciò, è necessario creare una metaclasse e definirne il __call__metodo.

Una metaclasse è solo la classe di una classe. E un __call__metodo di classe controlla cosa succede quando chiamate istanze della classe. Così un metaclasse ' __call__controlli di metodo che cosa succede quando si chiama una classe; cioè ti permette di ridefinire il meccanismo di creazione dell'istanza dall'inizio alla fine . Questo è il livello al quale è possibile implementare in modo elegante un processo di creazione di istanze completamente non standard come il modello singleton. In realtà, con meno di 10 righe di codice è possibile implementare un Singletonmetaclasse che poi non ha nemmeno richiedono di Futz con __new__ affatto , e in grado di trasformare qualsiasi classe altrimenti-normale in una Singleton con la semplice aggiunta __metaclass__ = Singleton!

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

Tuttavia, questa è probabilmente una magia più profonda di quanto sia veramente garantito per questa situazione!


25

Per citare la documentazione :

Le implementazioni tipiche creano una nuova istanza della classe richiamando il metodo __new __ () della superclasse usando "super (currentclass, cls) .__ new __ (cls [, ...])" con argomenti appropriati e modificando quindi l'istanza appena creata prima di restituirlo.

...

Se __new __ () non restituisce un'istanza di cls, il metodo __init __ () della nuova istanza non verrà richiamato.

__new __ () ha principalmente lo scopo di consentire alle sottoclassi di tipi immutabili (come int, str o tuple) di personalizzare la creazione dell'istanza.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Questo è un punto importante. Se si restituisce un'istanza diversa, l'originale __init__non viene mai chiamato.
Jeffrey Jose,

@tgray Mi rendo conto che la tua risposta proviene dai documenti, ma sono curioso di sapere se conosci qualche caso d'uso di non restituire un'istanza di cls. Sembra che quando l'oggetto restituito di __new__viene verificato per la sua classe, il metodo genererebbe un errore piuttosto che consentire al controllo non riuscito di passare silenziosamente, perché non capisco perché si vorrebbe mai restituire altro che un oggetto della classe .
soporific312

@ soporific312 Non ho visto alcun caso d'uso. Questa risposta discute alcuni dei ragionamenti per il design, anche se non hanno visto alcun codice che sfrutti quella "caratteristica".
Tgray,

13

Mi rendo conto che questa domanda è piuttosto vecchia ma ho avuto un problema simile. Quanto segue ha fatto quello che volevo:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

Ho usato questa pagina come risorsa http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
Nota: __new__ restituisce sempre un oggetto appropriato, quindi __init__ viene sempre chiamato, anche se l'istanza esiste già.
Pensando in modo strano il

10

Penso che la semplice risposta a questa domanda sia che, se __new__restituisce un valore dello stesso tipo della classe, la __init__funzione viene eseguita, altrimenti non lo farà. In questo caso il tuo codice restituisce A._dict('key')la stessa classe di cls, quindi __init__verrà eseguito.


10

Quando __new__restituisce un'istanza della stessa classe, __init__viene eseguita successivamente sull'oggetto restituito. Vale a dire che NON è possibile utilizzare __new__per impedire __init__l'esecuzione. Anche se si restituisce l'oggetto creato in precedenza __new__, sarà doppio (triplo, ecc ...) inizializzato __init__ancora e ancora.

Ecco l'approccio generico al modello Singleton che estende la risposta vartec sopra e la risolve:

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

La storia completa è qui .

Un altro approccio, che in realtà comporta __new__è l'uso di metodi di classe:

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

Ti preghiamo di prestare attenzione al fatto che con questo approccio devi decorare TUTTI i tuoi metodi @classmethod, perché non userai mai alcuna istanza reale di MyClass.


6

Facendo riferimento a questo documento :

Quando si esegue la sottoclasse di tipi incorporati immutabili come numeri e stringhe e, occasionalmente, in altre situazioni, il nuovo metodo statico risulta utile. nuovo è il primo passo nella costruzione dell'istanza, invocato prima di init .

Il nuovo metodo viene chiamato con la classe come primo argomento; la sua responsabilità è di restituire una nuova istanza di quella classe.

Confrontalo con init : init viene chiamato con un'istanza come primo argomento e non restituisce nulla; la sua responsabilità è di inizializzare l'istanza.

Ci sono situazioni in cui viene creata una nuova istanza senza chiamare init (ad esempio quando l'istanza viene caricata da un pickle). Non è possibile creare una nuova istanza senza chiamare new (anche se in alcuni casi è possibile cavarsela chiamando la nuova classe di base ).

Per quanto riguarda ciò che desideri ottenere, ci sono anche nelle stesse informazioni doc sul modello Singleton

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

puoi anche usare questa implementazione da PEP 318, usando un decoratore

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

@singleton
class MyClass:
...

1
Chiamare una forma bastardata initda __new__suoni davvero schizzinosi. Ecco a cosa servono i metaclassi.
Fisico pazzo,

6
class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

uscite:

NEW
INIT

NEW
INIT

EXISTS

NB Poiché la M._dictproprietà di un effetto collaterale diventa automaticamente accessibile da Ain A._dicttal modo, fare attenzione a non sovrascriverla per inciso.


Ti manca un __init__metodo che imposta cls._dict = {}. Probabilmente non vuoi un dizionario condiviso da tutte le classi di questo metatipo (ma +1 per l'idea).
Fisico pazzo,

5

__new__ dovrebbe restituire una nuova istanza vuota di una classe. Viene quindi chiamato __init__ per inizializzare quell'istanza. Non stai chiamando __init__ nel caso "NUOVO" di __new__, quindi viene chiamato per te. Il codice che chiama __new__non tiene traccia del fatto che __init__ sia stato chiamato in una particolare istanza o no, perché qui stai facendo qualcosa di molto insolito.

È possibile aggiungere un attributo all'oggetto nella funzione __init__ per indicare che è stato inizializzato. Controlla l'esistenza di quell'attributo come prima cosa in __init__ e non procedere oltre se lo è stato.


5

Un aggiornamento alla risposta di @AntonyHatchkins, probabilmente vuoi un dizionario separato di istanze per ogni classe del metatipo, il che significa che dovresti avere un __init__metodo nella metaclasse per inizializzare l'oggetto della classe con quel dizionario invece di renderlo globale in tutte le classi.

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

Sono andato avanti e ho aggiornato il codice originale con un __init__metodo e ho modificato la sintassi in notazione Python 3 (chiamata no-arg supere metaclasse negli argomenti di classe anziché come attributo).

In entrambi i casi, il punto importante qui è che il vostro inizializzatore di classe ( __call__metodo) non verrà eseguito sia __new__o __init__se viene trovato la chiave. Questo è molto più pulito dell'uso __new__, il che richiede di contrassegnare l'oggetto se si desidera saltare il __init__passaggio predefinito .


4

Scavando un po 'più a fondo in quello!

Il tipo di una classe generica in CPython è typee la sua classe base è Object(a meno che non si definisca esplicitamente un'altra classe base come una metaclasse). La sequenza di chiamate di basso livello è disponibile qui . Il primo metodo chiamato è quello type_callche quindi chiama tp_newe quindi tp_init.

La parte interessante qui è che tp_newchiamerà il Objectnuovo metodo della (classe base) object_newche fa un tp_alloc( PyType_GenericAlloc) che alloca la memoria per l'oggetto :)

A quel punto l'oggetto viene creato in memoria e quindi __init__viene chiamato il metodo. Se __init__non è implementato nella tua classe, object_initviene chiamato e non fa nulla :)

Quindi type_callrestituisce semplicemente l'oggetto che si lega alla variabile.


4

Si dovrebbe considerare __init__un semplice costruttore nei linguaggi OO tradizionali. Ad esempio, se si ha familiarità con Java o C ++, al costruttore viene passato implicitamente un puntatore alla propria istanza. Nel caso di Java, è la thisvariabile. Se si ispezionasse il codice byte generato per Java, si noterebbero due chiamate. La prima chiamata è a un "nuovo" metodo, quindi la chiamata successiva è al metodo init (che è la chiamata effettiva al costruttore definito dall'utente). Questo processo in due passaggi consente la creazione dell'istanza effettiva prima di chiamare il metodo di costruzione della classe che è solo un altro metodo di tale istanza.

Ora, nel caso di Python, __new__è stata aggiunta una funzione accessibile all'utente. Java non offre tale flessibilità, a causa della sua natura tipizzata. Se un linguaggio forniva tale funzione, l'implementatore di __new__potrebbe fare molte cose in quel metodo prima di restituire l'istanza, inclusa la creazione in alcuni casi di un'istanza totalmente nuova di un oggetto non correlato. E, questo approccio funziona bene anche per i tipi immutabili nel caso di Python.


4

Tuttavia, sono un po 'confuso sul perché __init__viene sempre chiamato dopo __new__.

Penso che l'analogia C ++ sarebbe utile qui:

  1. __new__alloca semplicemente la memoria per l'oggetto. Le variabili di istanza di un oggetto hanno bisogno di memoria per trattenerlo, e questo è ciò che __new__farebbe il passaggio .

  2. __init__ inizializza le variabili interne dell'oggetto su valori specifici (potrebbe essere predefinito).


2

La __init__si chiama dopo __new__in modo che quando si modifica in una sottoclasse, il codice aggiunto sarà ancora ottenere chiamato.

Se si sta tentando di sottoclassare una classe che ha già una classe __new__, qualcuno ignaro di ciò potrebbe iniziare adattando __init__e inoltrando la chiamata alla sottoclasse __init__. Questa convenzione di chiamare __init__dopo __new__aiuta a funzionare come previsto.

Deve __init__comunque consentire tutti i parametri __new__necessari alla superclasse , ma in caso contrario, si creerà un chiaro errore di runtime. E __new__probabilmente dovrebbe consentire esplicitamente *argse '** kw', per chiarire che l'estensione è OK.

In genere è una cattiva forma avere entrambi __new__e __init__nella stessa classe allo stesso livello di eredità, a causa del comportamento descritto dal poster originale.


1

Tuttavia, sono un po 'confuso sul perché __init__viene sempre chiamato dopo __new__.

Non c'è molto altro da dire che è fatto così. __new__non ha la responsabilità di inizializzare la classe, qualche altro metodo lo fa ( __call__, forse-- non lo so per certo).

Non me lo aspettavo. Qualcuno può dirmi perché questo sta accadendo e come posso implementare questa funzionalità altrimenti? (a parte mettere l'implementazione in quello __new__che sembra abbastanza confuso).

Non potresti __init__fare nulla se è già stato inizializzato o potresti scrivere una nuova metaclasse con una nuova __call__che richiama solo __init__nuove istanze e che altrimenti ritorni __new__(...).


1

Il semplice motivo è che il nuovo viene utilizzato per creare un'istanza, mentre init viene utilizzato per inizializzare l'istanza. Prima di inizializzare, l'istanza deve essere creata per prima. Ecco perché è necessario chiamare new prima di init .


1

Ora ho lo stesso problema e per alcuni motivi ho deciso di evitare decoratori, fabbriche e metaclassi. L'ho fatto così:

File principale

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

Classi di esempio

class A(Parent):

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


class B(Parent):

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

In uso

>>> 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>
  • Attenzione: Si dovrebbe Capogruppo non inizializzazione, che si scontrano con altre classi - a meno che non si è definito la cache separata in ciascuno dei figli, che non è quello che vogliamo.
  • Avvertenza: sembra che una classe con il genitore come nonno si comporti in modo strano. [Non verificato]

Provalo online!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.