Python (e API Python C): __new__ versus __init__


126

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

Respostas:


137

A diferença surge principalmente com tipos mutáveis ​​vs imutáveis.

__new__aceita um tipo como o primeiro argumento e (geralmente) retorna uma nova instância desse tipo. Portanto, é adequado para uso com tipos mutáveis ​​e imutáveis.

__init__aceita uma instância como o primeiro argumento e modifica os atributos dessa instância. Isso é inapropriado para um tipo imutável, pois permitiria que eles fossem modificados após a criação, chamando obj.__init__(*args).

Compare o comportamento de tuplee list:

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

Por que eles estão separados (além de simples razões históricas): os __new__métodos exigem um monte de clichê para acertar (a criação inicial do objeto e, em seguida, lembrando-se de retornar o objeto no final). __init__Os métodos, por outro lado, são simples, pois você apenas define os atributos que precisa definir.

Além de os __init__métodos serem mais fáceis de escrever, e a distinção entre mutável e imutável observada acima, a separação também pode ser explorada para tornar __init__opcional a chamada da classe pai nas subclasses, configurando quaisquer invariantes de instância absolutamente necessários __new__. Geralmente, essa é uma prática duvidosa - geralmente é mais claro chamar os __init__métodos de classe pai conforme necessário.


1
o código a que você se refere como "clichê" __new__não é clichê, porque o clichê nunca muda. Às vezes, você precisa substituir esse código específico por algo diferente.
Miles Rout

13
Criar ou adquirir a instância (geralmente com uma superchamada) 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.
ncoghlan

37

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 e size.

class ModularTuple(tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

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.


1
Eu não entendo por que devemos usar super? Quero dizer, por que o novo deve retornar uma instância da superclasse? Além disso, como você colocou, por que devemos passar cls explicitamente para new ? super (ModularTuple, cls) não retorna um método vinculado?
Alcott

3
@ Alcott, acho que você está entendendo mal o comportamento de __new__. Passamos clsexplicitamente 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 de cls. Nesse caso, é exatamente como se tivéssemos dito tuple.__new__(ModularTuple, tup).
Senderle

35

__new__()pode retornar objetos de tipos diferentes da classe à qual está vinculado. __init__()somente inicializa uma instância existente da classe.

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5

Esta é a explicação mais enxuta até agora.
Tarik

Não é bem verdade. Eu tenho __init__métodos que contêm código que se parece self.__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 um intcomo você fez ... Recebo um erro sobre tipos de heap ou algo assim ... mas meu exemplo de atribuí-lo a uma classe criada dinamicamente funciona.
ArtOfWarfare

Eu também estou confuso sobre quando __init__()é chamado. Por exemplo, na resposta da lonetwin , uma Triangle.__init__()ou Square.__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, pois Shape.__new__() não está retornando uma instância de cls(nem uma de uma subclasse dela).
martineau

1
@martineau: Os __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 quando Shape.__new__()retorna.
Ignacio Vazquez-Abrams

Ahh, certo, Shape.__init__()(se tivesse um) não seria chamado. Agora é tudo que faz mais sentido ...:¬)
martineau

13

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__).


Eu chamaria init de new se quisesse que a memória fosse alocada e os dados fossem inicializados? Por que se new não existe ao criar a instância, o init é chamado?
Red22_15 /

2
O trabalho do __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.
Noufal Ibrahim

3

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:

Portanto, o único benefício do método __new__ é que a variável de instância começará como uma sequência vazia, em vez de NULL. Mas por que isso é sempre útil, uma vez que, se nos preocupamos em garantir que nossas variáveis ​​de instância sejam inicializadas com algum valor padrão, poderíamos ter feito isso no método __init__?

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:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

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__).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.