Por que __init __ () sempre é chamado após __new __ ()?


568

Estou apenas tentando simplificar uma das minhas classes e introduzimos algumas funcionalidades no mesmo estilo que o padrão de design do flyweight .

No entanto, estou um pouco confuso sobre o motivo de __init__sempre ser chamado depois __new__. Eu não estava esperando isso. Alguém pode me dizer por que isso está acontecendo e como posso implementar essa funcionalidade de outra forma? (Além de colocar a implementação na __new__qual parece bastante invasiva.)

Aqui está um exemplo:

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

Saídas:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Por quê?


estava tentando entender o padrão de design também, e ouvi falar pela primeira vez: padrão de design com peso de mosca ... e um link muito bom com exemplo em quase todos os idiomas populares.
EmmaYang

Respostas:


598

Use __new__quando você precisar controlar a criação de uma nova instância.

Use __init__quando você precisar controlar a inicialização de uma nova instância.

__new__é o primeiro passo da criação da instância. É chamado primeiro e é responsável por retornar uma nova instância da sua classe.

Por outro lado, __init__não retorna nada; é responsável apenas pela inicialização da instância após a criação.

Em geral, você não deve substituir, a __new__menos que esteja subclassificando um tipo imutável como str, int, unicode ou tupla.

Desde a publicação de abril de 2008: quando usar __new__x __init__? em mail.python.org.

Você deve considerar que o que você está tentando fazer geralmente é feito com uma fábrica e essa é a melhor maneira de fazê-lo. O uso __new__não é uma boa solução limpa, portanto, considere o uso de uma fábrica. Aqui você tem um bom exemplo de fábrica .


1
Acabou usando __new__dentro de uma classe Factory, que ficou bastante limpa, então obrigado pela sua contribuição.
2525 Dan

11
Desculpe, discordo que o uso de __new__deve ser estritamente limitado aos casos declarados. Eu achei muito útil para implementar fábricas de classes genéricas extensíveis - veja minha resposta para a pergunta Uso inadequado de __new__para gerar classes em Python? para um exemplo de como fazê-lo.
27515

170

__new__é um método de classe estática, enquanto __init__é um método de instância. __new__precisa criar a instância primeiro, para __init__inicializá-la. Observe que __init__assume selfcomo parâmetro. Até você criar uma instância, não háself .

Agora, entendo, que você está tentando implementar o padrão singleton no Python. Existem algumas maneiras de fazer isso.

Além disso, a partir do Python 2.6, você pode usar decoradores de 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, eu não entendo direito como funciona o "@singleton"? Porque o decorador retorna uma função, mas MyClass é uma classe.
Alcott

11
Por que o dicionário? Como cls sempre será o mesmo e você obterá um novo dicionário, por exemplo, para cada singleton, você estará criando um dicionário com apenas um item.
Winston Ewert

7
@Alcott: Nenhuma opinião é necessária - os documentos concordam com você.
Ethan Furman

2
@Alcott. sim, o decorador retorna uma função. mas tanto a classe quanto a função são passíveis de chamada. Eu acho que instâncias = {} devem ser uma variável global.
Tyler Longo

4
@TylerLong Alcott tem um bom argumento. Isso resulta no nome MyClasssendo vinculado a uma função que retorna instâncias da classe original. Mas agora não há como se referir à classe original e, MyClasssendo uma função quebras isinstance/ issubclassverificações, acesso diretamente aos atributos / métodos MyClass.somethingda classe, como nomear a classe em superchamadas, etc. etc. Se isso é um problema ou não, depende do classe à qual você está aplicando (e o restante do programa).
Ben

148

Na maioria das linguagens OO conhecidas, uma expressão como SomeClass(arg1, arg2)alocará uma nova instância, inicializará os atributos da instância e a retornará.

Na maioria das linguagens OO conhecidas, a parte "inicializar os atributos da instância" pode ser personalizada para cada classe, definindo um construtor , que é basicamente apenas um bloco de código que opera na nova instância (usando os argumentos fornecidos para a expressão do construtor ) para configurar as condições iniciais desejadas. No Python, isso corresponde ao __init__método da classe .

O Python __new__não é nada mais e nada menos que a customização por classe semelhante da parte "alocar uma nova instância". É claro que isso permite que você faça coisas incomuns, como retornar uma instância existente, em vez de alocar uma nova. Portanto, em Python, não devemos realmente pensar nessa parte como necessariamente envolvendo alocação; tudo o que exigimos é que __new__surja uma instância adequada de algum lugar.

Mas ainda é apenas metade do trabalho, e não há como o sistema Python saber que às vezes você deseja executar a outra metade do trabalho ( __init__) depois e às vezes não. Se você deseja esse comportamento, deve dizê-lo explicitamente.

Freqüentemente, você pode refatorar para que você só precise __new__ou não precise __new__, ou que __init__se comporte de maneira diferente em um objeto já inicializado. Mas se você realmente quiser, o Python realmente permite que você redefina "o trabalho", de modo que SomeClass(arg1, arg2)não necessariamente chame __new__seguido por __init__. Para fazer isso, você precisa criar uma metaclasse e definir sua__call__ método.

Uma metaclasse é apenas a classe de uma classe. E o __call__método de uma classe controla o que acontece quando você chama instâncias da classe. Portanto , o método de uma metaclasse__call__ controla o que acontece quando você chama uma classe; isto é, permite redefinir o mecanismo de criação da instância do início ao fim . Esse é o nível no qual você pode implementar com mais elegância um processo de criação de instância completamente fora do padrão, como o padrão singleton. Na verdade, com menos de 10 linhas de código que você pode implementar uma Singletonmetaclasse que, em seguida, nem sequer exigem que você futz com __new__ em tudo , e pode transformar qualquer classe de outra forma normal em um singleton simplesmente adicionando __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

No entanto, essa provavelmente é uma mágica mais profunda do que realmente é necessária para esta situação!


25

Para citar a documentação :

Implementações típicas criam uma nova instância da classe chamando o método __new __ () da superclasse usando "super (currentclass, cls) .__ new __ (cls [, ...])" com argumentos apropriados e modificando a instância recém-criada conforme necessário antes de devolvê-lo.

...

Se __new __ () não retornar uma instância de cls, o método __init __ () da nova instância não será chamado.

__new __ () destina-se principalmente a permitir que subclasses de tipos imutáveis ​​(como int, str ou tupla) personalizem a criação da instância.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Esse é um ponto importante. Se você retornar uma instância diferente, o original __init__nunca será chamado.
21412 Jeffrey Jose

@tgray Sei que sua resposta é dos documentos, mas estou curioso para saber se existe algum caso de uso de não retornar uma instância de cls. Parece que quando o objeto retornado de __new__é verificado em relação a sua classe, o método gera um erro, em vez de permitir que a verificação falhada passe silenciosamente, porque eu não entendo por que você desejaria retornar algo além de um objeto da classe .
soporific312

@ soporific312 Não vi nenhum caso de uso. Esta resposta discute alguns dos motivos do design, embora eles também não tenham visto nenhum código que aproveite esse "recurso".
tgray 28/04

12

Sei que essa pergunta é bastante antiga, mas tive um problema semelhante. O seguinte fez o que eu queria:

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

Usei esta página como um recurso http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
Nota: __new__ sempre retorna um objeto apropriado, portanto __init__ é sempre chamado - mesmo que a instância já exista.
Oddthinking

10

Eu acho que a resposta simples para essa pergunta é que, se __new__retornar um valor do mesmo tipo que a classe, a __init__função será executada, caso contrário, não será. Nesse caso, seu código retorna A._dict('key')a mesma classe que cls, portanto, __init__será executado.


10

Quando __new__retorna instância da mesma classe, __init__é executado posteriormente no objeto retornado. Ou seja, você NÃO pode usar __new__para impedir que __init__seja executado. Mesmo se você retornar um objeto criado anteriormente __new__, ele será duplo (triplo, etc ...) inicializado __init__várias vezes.

Aqui está a abordagem genérica do padrão Singleton, que estende a resposta vartec acima e a corrige:

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

A história completa está aqui .

Outra abordagem, que de fato envolve __new__é o uso de métodos de 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"

Por favor, preste atenção, que com essa abordagem você precisa decorar TODOS os seus métodos @classmethod, porque você nunca usará nenhuma instância real de MyClass.


6

Referindo-se a este documento :

Ao subclassificar tipos internos imutáveis, como números e seqüências de caracteres, e ocasionalmente em outras situações, o novo método estático é útil. new é o primeiro passo na construção da instância, invocada antes do init .

O novo método é chamado com a classe como seu primeiro argumento; sua responsabilidade é retornar uma nova instância dessa classe.

Compare isso com o init : init é chamado com uma instância como seu primeiro argumento e não retorna nada; sua responsabilidade é inicializar a instância.

Há situações em que uma nova instância é criada sem chamar init (por exemplo, quando a instância é carregada de um pickle). Não há como criar uma nova instância sem chamar new (embora em alguns casos você possa chamar a new de uma classe base ).

Com relação ao que você deseja alcançar, também há informações no mesmo documento sobre o padrão 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

você também pode usar esta implementação do PEP 318, usando um decorador

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

@singleton
class MyClass:
...

1
Chamar uma forma bastardizada de initde __new__sons realmente hacky. É para isso que servem as metaclasses.
May Physicist

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

saídas:

NEW
INIT

NEW
INIT

EXISTS

NB Como uma M._dictpropriedade de efeito colateral se torna automaticamente acessível A, A._dicttome cuidado para não substituí-la acidentalmente.


Está faltando um __init__método que define cls._dict = {}. Você provavelmente não deseja que um dicionário seja compartilhado por todas as classes desse metatipo (exceto +1 para a ideia).
May Physicist

5

__new__ deve retornar uma nova instância em branco de uma classe. __init__ é então chamado para inicializar essa instância. Você não está chamando __init__ no caso "NEW" de __new__, portanto está sendo chamado por você. O código que está chamando __new__não controla se __init__ foi chamado em uma instância específica ou não, nem deveria, porque você está fazendo algo muito incomum aqui.

Você pode adicionar um atributo ao objeto na função __init__ para indicar que ele foi inicializado. Verifique a existência desse atributo como a primeira coisa em __init__ e não continue mais, se tiver sido.


5

Uma atualização para a resposta @AntonyHatchkins, você provavelmente deseja um dicionário de instâncias separado para cada classe do metatipo, o que significa que você deve ter um __init__método na metaclasse para inicializar seu objeto de classe com esse dicionário, em vez de torná-lo global em todas as classes.

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

Fui em frente e atualizei o código original com um __init__método e alterei a sintaxe para notação Python 3 (chamada no-arg supere metaclasse nos argumentos da classe em vez de como um atributo).

De qualquer forma, o ponto importante aqui é que o inicializador de classe ( __call__método) não será executado __new__ou __init__se a chave for encontrada. Isso é muito mais limpo do que usar __new__, o que requer que você marque o objeto se desejar pular a __init__etapa padrão .


4

Indo um pouco mais fundo nisso!

O tipo de uma classe genérica no CPython é typee sua classe base é Object(a menos que você defina explicitamente outra classe base como uma metaclasse). A sequência de chamadas de baixo nível pode ser encontrada aqui . O primeiro método chamado é o type_callque chama tp_newe depois tp_init.

A parte interessante aqui é que tp_newchamaremos o Objectnovo método (classe base) object_newque faz a tp_alloc( PyType_GenericAlloc) que aloca a memória para o objeto :)

Nesse ponto, o objeto é criado na memória e, em seguida, o __init__método é chamado. Se __init__não for implementado em sua classe, o object_initchamado será chamado e não fará nada :)

Em seguida, type_callbasta retornar o objeto que se liga à sua variável.


4

Deve-se considerar __init__um construtor simples nas linguagens OO tradicionais. Por exemplo, se você estiver familiarizado com Java ou C ++, o construtor passará um ponteiro para sua própria instância implicitamente. No caso de Java, é othis variável Se alguém inspecionasse o código de bytes gerado para Java, notaria duas chamadas. A primeira chamada é para um método "novo" e a próxima chamada é para o método init (que é a chamada real para o construtor definido pelo usuário). Esse processo de duas etapas permite a criação da instância real antes de chamar o método construtor da classe, que é apenas outro método dessa instância.

Agora, no caso do Python, __new__há um recurso adicional que é acessível ao usuário. Java não fornece essa flexibilidade, devido à sua natureza digitada. Se uma linguagem fornecesse esse recurso, o implementador de __new__poderia fazer várias coisas nesse método antes de retornar a instância, incluindo a criação de uma instância totalmente nova de um objeto não relacionado em alguns casos. E, essa abordagem também funciona bem especialmente para tipos imutáveis ​​no caso do Python.


4

No entanto, estou um pouco confuso sobre o motivo de __init__sempre ser chamado depois __new__.

Eu acho que a analogia C ++ seria útil aqui:

  1. __new__simplesmente aloca memória para o objeto. As variáveis ​​de instância de um objeto precisam de memória para mantê-lo, e é isso que a etapa __new__faria.

  2. __init__ inicialize as variáveis ​​internas do objeto para valores específicos (pode ser o padrão).


2

O __init__é chamado depois __new__para que, quando você o substitua em uma subclasse, seu código adicionado ainda seja chamado.

Se você está tentando subclassificar uma classe que já possui uma __new__, alguém que não sabe disso pode começar adaptando __init__e encaminhando a chamada para a subclasse __init__. Esta convenção de chamar __init__depois__new__ ajuda o trabalho conforme o esperado.

O __init__ainda precisa permitir quaisquer parâmetros __new__necessários para a superclasse , mas não fazer isso normalmente criará um erro de tempo de execução claro. E o __new__provavelmente deve permitir explicitamente *argse '** kw', para deixar claro que a extensão está OK.

Geralmente, é péssimo ter ambos __new__e __init__na mesma classe no mesmo nível de herança, devido ao comportamento descrito no pôster original.


1

No entanto, estou um pouco confuso sobre o motivo de __init__sempre ser chamado depois __new__.

Não há muito outro motivo além do que é feito dessa maneira. __new__não tem a responsabilidade de inicializar a classe, algum outro método possui ( __call__, possivelmente - não sei ao certo).

Eu não estava esperando isso. Alguém pode me dizer por que isso está acontecendo e como implementar essa funcionalidade de outra forma? (além de colocar a implementação na __new__qual parece bastante hacky).

Você não pode __init__fazer nada se já tiver sido inicializado ou escrever uma nova metaclasse com uma nova __call__que apenas invoque __init__novas instâncias e, caso contrário, retorne __new__(...).


1

O motivo simples é que o novo é usado para criar uma instância, enquanto o init é usado para inicializar a instância. Antes de inicializar, a instância deve ser criada primeiro. É por isso que novo deve ser chamado antes do init .


1

Agora, tenho o mesmo problema e, por alguns motivos, decidi evitar decoradores, fábricas e metaclasses. Eu fiz assim:

Arquivo principal

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

Classes de exemplo

class A(Parent):

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


class B(Parent):

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

Em 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>
  • Aviso: Você não deve inicializar o Pai, pois ele colidir com outras classes - a menos que você definiu cache separado em cada uma das crianças, isso não é o que queremos.
  • Aviso: Parece que uma turma com os Pais como avô se comporta de maneira estranha. [Não verificado]

Experimente online!

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.