In Python 2, non si può davvero chiamare un metodo regolare con il primo argomento di essere qualcosa di diverso da un'istanza della classe (o una sottoclasse):
class Foo(object):
def __init__(self):
pass
Foo.__init__()
# TypeError: unbound method __init__() must be called with Foo instance as first argument (got nothing instead)
Foo.__init__(3)
# TypeError: unbound method __init__() must be called with Foo instance as first argument (got int instance instead)
Quindi __init__
non si chiama perché non può possibilmente fare qualsiasi altra cosa che sollevare immediatamente un'eccezione. Non cercare di chiamarlo è strettamente più utile (anche se non penso di aver mai visto il codice approfittarne).
Python 3 ha un'implementazione del metodo leggermente più semplice e questa restrizione non è più disponibile, ma la semantica __new__
è la stessa. Non ha senso cercare di eseguire l'inizializzatore di una classe su un oggetto esterno, comunque.
Per una risposta più designy, piuttosto che un "perché è in questo modo" risposta:
Override __new__
è già una cosa strana da fare. Di default, restituisce un oggetto non inizializzato, che è un concetto che Python cerca molto difficile da nascondere. Se si ignora che, probabilmente stai facendo qualcosa di simile:
class Foo(object):
def __new__(cls, some_arg):
if some_arg == 15:
# 15 is a magic number for some reason!
return Bar()
else:
return super(Foo, cls).__new__(cls, some_arg)
Immaginiamo una variante Python che incondizionatamente chiamato __init__
sul valore di ritorno. Vedo subito una serie di problemi.
Quando ritorni Bar()
, dovrebbe Python chiamata Bar.__init__
(che è già stato chiamato in questo caso) o Foo.__init__
(che è per un tipo completamente diverso e si rompono qualunque garanzie Bar
fa)?
La risposta deve essere sicuramente quella che viene chiamata Bar.__init__
. Significa che devi restituire un'istanza non inizializzata Bar
, utilizzando invece il boccone return Bar.__new__(Bar)
? Molto raramente Python richiede di chiamare i metodi dunder al di fuori dell'uso di super
, quindi sarebbe molto insolito.
Da dove provengono gli argomenti di Bar.__init__
? Sia Foo.__new__
e Foo.__init__
vengono passati gli stessi argomenti - quelli passati a type.__call__
, che è ciò che gestisce Foo(...)
. Ma se chiami esplicitamente Bar.__new__
, non c'è nessun luogo in cui ricordare gli argomenti che si desidera passare a Bar.__init__
.Non è possibile memorizzarli sul nuovo oggetto Bar
, perché è ciò che si suppone che lo sia Bar.__init__
! E se hai appena detto che ottiene gli stessi argomenti passati a Foo
, limiti severamente i tipi che possono essere restituiti da __new__
.
Oppure, cosa succede se si desidera restituire un oggetto che esiste già? Python non ha modo di indicare che un oggetto è "già" inizializzato - dal momento che gli oggetti non inizializzati sono una cosa transitoria e per lo più interna solo di interesse per __new__
- quindi non avresti modo di dire di non chiamare di nuovo __init__
.
L'approccio attuale è un po 'goffo, ma non penso ci sia un'alternativa migliore. __new__
è supposto per creare spazio di archiviazione per il nuovo oggetto e restituire un diverso tipo di oggetto completamente è solo una cosa davvero strana da fare; questo è il modo meno sorprendente e più utile in cui Python può gestirlo.
Se questa limitazione si sta intralciando, ricorda che l'intera danza __new__
e __init__
è solo il comportamento di type.__call__
. Sei perfettamente libero di definire il tuo comportamento __call__
su una metaclass o semplicemente di scambiare la tua classe con una funzione di fabbrica.
Perché "__init__" dovrebbe sempre essere invocato? –
Non ho mai detto che dovrebbe. Mi sto solo chiedendo perché sia così. – DanielSank
La mia ipotesi sarebbe che, se '__new__' non restituisce un'istanza di' cls', Python non ha un valore per 'self' da passare a' __init__'. –