risposta-alla-domanda-sullo-sviluppo-web-bd.com

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

Sto solo cercando di razionalizzare uno dei miei corsi e ho introdotto alcune funzionalità nello stesso stile del modello di disegno flyweight .

Tuttavia, sono un po 'confuso sul motivo per cui __init__ viene sempre chiamato dopo __new__. Non me lo aspettavo. Qualcuno può dirmi perché questo sta accadendo e come posso implementare questa funzionalità in altro modo? (Oltre a mettere l'implementazione in __new__ che si sente abbastanza hacky.)

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é?

480
Dan

Usa _NEW_ quando devi controllare la creazione di una nuova istanza. Uso _INIT_ quando è necessario controllare l'inizializzazione di una nuova istanza.

_NEW_ è il primo passo della creazione dell'istanza. Si chiama prima ed è responsabile della restituzione di un nuovo istanza della tua classe. In contrasto, _INIT_ non restituisce nulla; è responsabile solo per inizializzare il istanza dopo che è stata creata.

In generale, non dovresti aver bisogno di sostituisci _NEW_ a meno che tu non sia sottoclasse un tipo immutabile come str, int, unicode o Tuple.

Da: http://mail.python.org/pipermail/tutor/2008-April/061426.html

Dovresti considerare che ciò che stai cercando di fare è di solito fatto con un Factory e questo è il modo migliore per farlo. L'uso di _NEW_ non è una buona soluzione pulita, quindi considera l'utilizzo di una fabbrica. Qui hai un buon esempio di fabbrica .

505
mpeterson

__new__ è un metodo di classe statico, mentre __init__ è un metodo di istanza. __new__ deve prima creare l'istanza, quindi __init__ può inizializzarlo. Nota che __init__ accetta self come parametro. Fino a quando non crei un'istanza non esiste self.

Ora, deduco, che stai cercando di implementare singleton pattern in Python. Ci sono alcuni modi per farlo.

Inoltre, a partire da Python 2.6, puoi usare class decoratori

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

@singleton
class MyClass:
  ...
155
vartec

Nella maggior parte dei ben noti linguaggi OO, un'espressione come SomeClass(arg1, arg2) allocherà una nuova istanza, inizializzerà gli attributi dell'istanza e quindi la restituirà.

Nella maggior parte delle ben note lingue OO, 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 ( utilizzando gli argomenti forniti per l'espressione del costruttore) per impostare le condizioni iniziali desiderate. In Python, questo corrisponde al metodo __init__ della classe.

Python's __new__ non è niente di più e niente di meno che una personalizzazione per classe simile della parte "allocate a new instance". Questo ovviamente ti permette di fare cose insolite come restituire un'istanza esistente piuttosto che assegnarne una nuova. Quindi, in Python, non dovremmo davvero pensare che questa parte implichi necessariamente una allocazione; tutto ciò di cui abbiamo bisogno è che __new__ fornisca 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 si desidera eseguire l'altra metà del lavoro (__init__) in seguito e talvolta no. Se vuoi questo comportamento, devi dirlo in modo esplicito.

Spesso, puoi refactoring, quindi hai solo bisogno di __new__, o così non hai bisogno di __new__, o così __init__ si comporta in modo diverso su un oggetto già inizializzato. Ma se lo vuoi davvero, Python ti permette effettivamente di ridefinire "il lavoro", così che SomeClass(arg1, arg2) non chiama necessariamente __new__ seguito da __init__. Per fare ciò, è necessario creare un metaclasse e definire il suo metodo __call__.

Una metaclasse è solo la classe di una classe. E un metodo __call__ di classe controlla cosa succede quando chiami le istanze della classe. Quindi un metodo metaclass '__call__ controlla cosa succede quando chiami una classe; cioè ti consente di ridefinire il meccanismo di creazione dell'istanza dall'inizio alla fine. Questo è il livello in cui è possibile implementare più elegantemente un processo di creazione di istanze completamente non standard, come il modello singleton. In effetti, con meno di 10 righe di codice è possibile implementare un metaclass Singleton che quindi non richiede nemmeno di utilizzare il valore __new__a tutti, e può trasformare any altrimenti-normale classe in un singleton semplicemente aggiungendo __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 realmente giustificata per questa situazione!

130
Ben

Per citare la documentazione :

Le implementazioni tipiche creano una nuova istanza della classe invocando il metodo __new __ () della superclasse che usa "super (currentclass, cls) .__ new __ (cls [ ...])" con argomenti appropriati e quindi modificare l'istanza appena creata come necessario prima di restituirla.

...

Se __new __ () non restituisce un'istanza di cls, quindi il nuovo il metodo __init __ () dell'istanza non verrà invocato.

__new __ () è inteso principalmente per consentire sottoclassi di immutabili tipi (come int, str o tuple) per personalizzare la creazione dell'istanza.

20
tgray

Mi rendo conto che questa domanda è piuttosto vecchia ma ho avuto un problema simile . Il seguente 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

10
Tobias

Quando __new__ restituisce l'istanza della stessa classe, __init__ viene eseguito in seguito sull'oggetto restituito. Cioè NON puoi usare __new__ per impedire che __init__ venga eseguito. Anche se si restituisce oggetto precedentemente creato da __new__, sarà doppio (triplo, ecc.) Inizializzato da __init__ ancora e ancora.

Ecco l'approccio generico al pattern Singleton che estende la risposta vartec sopra e lo 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 effetti coinvolge __new__ è quello di usare classmethods:

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"

Si prega di prestare attenzione, che con questo approccio è necessario decorare TUTTI i metodi con @classmethod, perché non si utilizzerà mai alcuna istanza reale di MyClass.

8
Zaar Hai

Penso che la semplice risposta a questa domanda sia che, se __new__ restituisce un valore dello stesso tipo della classe, la funzione __init__ viene eseguita, altrimenti non lo farà. In questo caso il tuo codice restituisceA._dict('key')che è della stessa classe dicls, quindi __init__ verrà eseguito.

8
PMN
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 Come effetto collaterale la proprietà M._dict diventa automaticamente accessibile da A come A._dict, quindi fai attenzione a non sovrascriverlo casualmente.

5
Antony Hatchkins

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

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

4
Andrew Wilkinson

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 metodo __init__ nel metaclass per inizializzare l'oggetto di classe con quel dizionario invece di renderlo globale su 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()

Ho proseguito e aggiornato il codice originale con un metodo __init__ e ho modificato la sintassi con la notazione di Python 3 (chiamata no-arg a super e metaclass negli argomenti di classe anziché come attributo).

In entrambi i casi, il punto importante qui è che l'inizializzatore della classe (metodo __call__) non eseguirà né __new____init__ se la chiave viene trovata. Questo è molto più pulito rispetto all'utilizzo di __new__, che richiede di contrassegnare l'oggetto se si desidera saltare il passaggio __init__ predefinito.

4
Mad Physicist

Uno dovrebbe guardare __init__ come un semplice costruttore nelle tradizionali lingue OO. Ad esempio, se si ha familiarità con Java o C++, il costruttore viene passato implicitamente un puntatore alla propria istanza. Nel caso di Java, è la variabile this. Se si dovesse esaminare il codice byte generato per Java, si noterebbero due chiamate. La prima chiamata è a un metodo "nuovo", quindi la prossima chiamata è al metodo init (che è la chiamata effettiva al costruttore definito dall'utente). Questo processo in due passaggi consente la creazione dell'istanza reale prima di chiamare il metodo di costruzione della classe che è solo un altro metodo di quell'istanza.

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

3
Guru Devanla

Facendo riferimento a questo documento

Quando sottoclassi tipi immutabili incorporati come numeri e stringhe, e occasionalmente in altre situazioni, il metodo statico new viene utile. new è il primo passo nella costruzione di istanza, invocato prima di init

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

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

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

Per quanto riguarda ciò che desideri raggiungere, ci sono anche le stesse informazioni 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

è possibile utilizzare questa implementazione anche da PEP 318, utilizzando un decoratore

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

@singleton
class MyClass:
...
3
octoback

Scavando più in profondità in quello!

Il tipo di una classe generica in CPython è type e la sua classe base è Object (a meno che tu non definisca esplicitamente un'altra classe base come una metaclasse). La sequenza di chiamate di basso livello può essere trovata qui . Il primo metodo chiamato è type_call che quindi chiama tp_new e quindi tp_init

La parte interessante qui è che tp_new chiamerà il Object '(classe base) nuovo metodo object_new che fa un tp_alloc (PyType_GenericAlloc) che alloca la memoria per l'oggetto :)

A quel punto l'oggetto viene creato in memoria e quindi viene chiamato il metodo __init__. Se __init__ non è implementato nella tua classe allora viene chiamato object_init e non fa nulla :)

Quindi type_call restituisce solo l'oggetto che si lega alla tua variabile.

3
xpapazaf

Il __init__ viene chiamato dopo __new__ in modo che quando lo si sovrascrive in una sottoclasse, il codice aggiunto verrà comunque chiamato.

Se stai provando a sottoclassi una classe che ha già un __new__, qualcuno che non è a conoscenza di ciò potrebbe iniziare adattare il __init__ e inoltrare la chiamata alla sottoclasse __init__. Questa convenzione di chiamata __init__ dopo __new__ aiuta a funzionare come previsto.

Il __init__ deve comunque tenere conto di tutti i parametri necessari per la superclasse __new__, ma in caso contrario si creerà di solito un errore di runtime. E il __new__ dovrebbe probabilmente consentire esplicitamente *args e '** kw', per chiarire che l'estensione è OK.

In genere è male avere sia __new__ che __init__ nella stessa classe allo stesso livello di ereditarietà, a causa del comportamento descritto dal poster originale.

1
Jay Obermark

Tuttavia, sono un po 'confuso sul motivo per cui init viene sempre chiamato dopo new .

Penso che l'analogia C++ sarebbe utile qui: (A) new semplicemente alloca la memoria per l'oggetto. Le variabili di istanza di un oggetto hanno bisogno di memoria per tenerlo, e questo è ciò che il passo new dovrebbe fare . (B) init inizializzare le variabili interne dell'oggetto a valori specifici (potrebbe essere predefinito).

1
HAltos

Tuttavia, sono un po 'confuso sul motivo per cui __init__ viene sempre chiamato dopo __new__.

Non è una buona ragione se non che è fatto in questo modo. __new__ non ha la responsabilità di inizializzare la classe, qualche altro metodo (__call__, forse-- non so per certo).

Non me lo aspettavo. Qualcuno può dirmi perché questo sta accadendo e come implementare questa funzionalità in caso contrario? (a parte mettere l'implementazione nel __new__ che si sente abbastanza hacky).

Potresti avere __init__ non fare nulla se è già stato inizializzato, o potresti scrivere un nuovo metaclasse con un nuovo __call__ che chiama solo __init__ su nuove istanze, e altrimenti restituisce solo __new__(...).

0
Devin Jeanpierre

Ora ho lo stesso problema, e per qualche ragione ho deciso di evitare decoratori, fabbriche e metaclassi. L'ho fatto in questo modo:

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: Non dovresti inizializzare Parent, will collide con altre classi - a meno che tu non abbia definito una cache separata in ciascuno dei bambini, non è quello che vogliamo.
  • Attenzione: Sembra una classe con Genitori come i nonni si comportano in modo strano. [Non verificato]

Provalo online!

0
RedClover

Il semplice motivo è che new è usato per creare un'istanza, mentre init è usato per inizializzare l'istanza. Prima di inizializzare, l'istanza dovrebbe essere creata per prima. Ecco perché new dovrebbe essere chiamato prima di init .

0
ZQ Hu