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ê?
Respostas:
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 .fonte
__new__
dentro de uma classe Factory, que ficou bastante limpa, então obrigado pela sua contribuição.__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.__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__
assumeself
como 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 .
fonte
MyClass
sendo vinculado a uma função que retorna instâncias da classe original. Mas agora não há como se referir à classe original e,MyClass
sendo uma função quebrasisinstance
/issubclass
verificações, acesso diretamente aos atributos / métodosMyClass.something
da classe, como nomear a classe emsuper
chamadas, etc. etc. Se isso é um problema ou não, depende do classe à qual você está aplicando (e o restante do programa).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 queSomeClass(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 umaSingleton
metaclasse 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
!No entanto, essa provavelmente é uma mágica mais profunda do que realmente é necessária para esta situação!
fonte
Para citar a documentação :
fonte
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.__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 .Sei que essa pergunta é bastante antiga, mas tive um problema semelhante. O seguinte fez o que eu queria:
Usei esta página como um recurso http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
fonte
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 retornaA._dict('key')
a mesma classe quecls
, portanto,__init__
será executado.fonte
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:
A história completa está aqui .
Outra abordagem, que de fato envolve
__new__
é o uso de métodos de classe: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 deMyClass
.fonte
Referindo-se a este documento :
Com relação ao que você deseja alcançar, também há informações no mesmo documento sobre o padrão Singleton
você também pode usar esta implementação do PEP 318, usando um decorador
fonte
init
de__new__
sons realmente hacky. É para isso que servem as metaclasses.saídas:
NB Como uma
M._dict
propriedade de efeito colateral se torna automaticamente acessívelA
,A._dict
tome cuidado para não substituí-la acidentalmente.fonte
__init__
método que definecls._dict = {}
. Você provavelmente não deseja que um dicionário seja compartilhado por todas as classes desse metatipo (exceto +1 para a ideia).__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.
fonte
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.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-argsuper
e 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 .fonte
Indo um pouco mais fundo nisso!
O tipo de uma classe genérica no CPython é
type
e 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 é otype_call
que chamatp_new
e depoistp_init
.A parte interessante aqui é que
tp_new
chamaremos oObject
novo método (classe base)object_new
que faz atp_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, oobject_init
chamado será chamado e não fará nada :)Em seguida,
type_call
basta retornar o objeto que se liga à sua variável.fonte
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.fonte
Eu acho que a analogia C ++ seria útil aqui:
__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.__init__
inicialize as variáveis internas do objeto para valores específicos (pode ser o padrão).fonte
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*args
e '** 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.fonte
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).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__(...)
.fonte
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 .
fonte
Agora, tenho o mesmo problema e, por alguns motivos, decidi evitar decoradores, fábricas e metaclasses. Eu fiz assim:
Arquivo principal
Classes de exemplo
Em uso
Experimente online!
fonte