A pergunta que estou prestes a fazer parece ser uma duplicata do uso de __new__ e __init__ pelo Python? , mas, independentemente disso, ainda não está claro para mim exatamente qual é a diferença prática entre __new__
e __init__
é.
Antes de você se apressar para me dizer que __new__
é para criar objetos e __init__
para inicializar objetos, deixe-me esclarecer: entendi. De fato, essa distinção é bastante natural para mim, já que tenho experiência em C ++, onde temos um novo posicionamento , que também separa a alocação de objetos da inicialização.
O tutorial da API do Python C explica assim:
O novo membro é responsável por criar (em vez de inicializar) objetos do tipo. É exposto no Python como o
__new__()
método. ... Um motivo para implementar um novo método é garantir os valores iniciais das variáveis da instância .
Então, sim - eu entendo o que __new__
faz, mas, apesar disso, ainda não entendo por que é útil no Python. O exemplo dado diz que __new__
pode ser útil se você deseja "garantir os valores iniciais das variáveis da instância". Bem, não é exatamente isso que __init__
fará?
No tutorial da API C, é mostrado um exemplo em que um novo Type (chamado "Noddy") é criado e a __new__
função do Type é definida. O tipo Noddy contém um membro da cadeia de caracteres chamado first
e esse membro da cadeia é inicializado em uma cadeia vazia da seguinte forma:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
.....
self->first = PyString_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
.....
}
Observe que, sem o __new__
método definido aqui, teríamos que usar PyType_GenericNew
, que simplesmente inicializa todos os membros da variável de instância para NULL. Portanto, o único benefício do __new__
método é que a variável de instância começará como uma sequência vazia, em oposição a NULL. Mas por que isso é útil, pois se nos importamos em garantir que nossas variáveis de instância sejam inicializadas com algum valor padrão, poderíamos ter feito isso no __init__
método?
fonte
__new__
não é clichê, porque o clichê nunca muda. Às vezes, você precisa substituir esse código específico por algo diferente.super
chamada) e retornar a instância são partes necessárias de qualquer__new__
implementação e o "clichê" a que me refiro. Por outro lado,pass
é uma implementação válida para__init__
- não há nenhum comportamento necessário.Provavelmente existem outros usos para,
__new__
mas há um realmente óbvio: você não pode subclassificar um tipo imutável sem usar__new__
. Por exemplo, digamos que você queira criar uma subclasse de tupla que possa conter apenas valores integrais entre 0 esize
.Você simplesmente não pode fazer isso com
__init__
- se você tentou modificarself
em__init__
, o intérprete se queixam de que você está tentando modificar um objeto imutável.fonte
__new__
. Passamoscls
explicitamente para__new__
porque, como você pode ler aqui,__new__
sempre exige um tipo como seu primeiro argumento. Em seguida, ele retorna uma instância desse tipo. Portanto, não estamos retornando uma instância da superclasse - estamos retornando uma instância decls
. Nesse caso, é exatamente como se tivéssemos ditotuple.__new__(ModularTuple, tup)
.__new__()
pode retornar objetos de tipos diferentes da classe à qual está vinculado.__init__()
somente inicializa uma instância existente da classe.fonte
__init__
métodos que contêm código que se pareceself.__class__ = type(...)
. Isso faz com que o objeto seja de uma classe diferente daquela que você pensou que estava criando. Na verdade, não posso transformá-lo em umint
como você fez ... Recebo um erro sobre tipos de heap ou algo assim ... mas meu exemplo de atribuí-lo a uma classe criada dinamicamente funciona.__init__()
é chamado. Por exemplo, na resposta da lonetwin , umaTriangle.__init__()
ouSquare.__init__()
automaticamente é chamada, dependendo do tipo de__new__()
retorno. Pelo que você diz em sua resposta (e eu li isso em outro lugar), não parece que nenhum deles deva, poisShape.__new__()
não está retornando uma instância decls
(nem uma de uma subclasse dela).__init__()
métodos na resposta do lonetwin são chamados quando os objetos individuais são instanciados (ou seja, quando o__new__()
método retorna), não quandoShape.__new__()
retorna.Shape.__init__()
(se tivesse um) não seria chamado. Agora é tudo que faz mais sentido ...:¬)
Não é uma resposta completa, mas talvez algo que ilustra a diferença.
__new__
sempre será chamado quando um objeto tiver que ser criado. Existem algumas situações em__init__
que não serão chamadas. Um exemplo é quando você remove objetos de um arquivo pickle, eles serão alocados (__new__
), mas não inicializados (__init__
).fonte
__new__
método é criar (isso implica alocação de memória) uma instância da classe e retorná-la. A inicialização é uma etapa separada e é o que geralmente é visível para o usuário. Faça uma pergunta separada se houver um problema específico que você esteja enfrentando.Só quero adicionar uma palavra sobre a intenção (em oposição ao comportamento) de definir
__new__
versus__init__
.Me deparei com essa questão (entre outras) quando estava tentando entender a melhor maneira de definir uma fábrica de classes. Percebi que uma das maneiras pelas quais
__new__
é conceitualmente diferente__init__
é o fato de que o benefício de__new__
é exatamente o que foi afirmado na pergunta:Considerando o cenário declarado, nos preocupamos com os valores iniciais das variáveis da instância quando a instância é na realidade uma classe em si. Portanto, se estamos criando dinamicamente um objeto de classe em tempo de execução e precisamos definir / controlar algo especial sobre as instâncias subseqüentes dessa classe sendo criada, definiremos essas condições / propriedades em um
__new__
método de uma metaclasse.Fiquei confuso sobre isso até pensar na aplicação do conceito, e não apenas no significado dele. Aqui está um exemplo que espero esclarecer a diferença:
Note que este é apenas um exemplo demonstartive. Existem várias maneiras de obter uma solução sem recorrer a uma abordagem de fábrica de classes, como acima, e mesmo se optarmos por implementá-la dessa maneira, há algumas pequenas ressalvas por questão de brevidade (por exemplo, declarar explicitamente a metaclasse )
Se você está criando uma classe regular (também conhecida como não-metaclasse),
__new__
não faz muito sentido, a menos que seja um caso especial como o cenário mutável versus imutável na resposta da resposta de ncoghlan (que é essencialmente um exemplo mais específico do conceito de definição os valores / propriedades iniciais da classe / tipo que está sendo criado via__new__
para ser inicializado via__init__
).fonte