Invocando implicitamente Inicializador da classe pai
-
23-09-2019 - |
Pergunta
class A(object):
def __init__(self, a, b, c):
#super(A, self).__init__()
super(self.__class__, self).__init__()
class B(A):
def __init__(self, b, c):
print super(B, self)
print super(self.__class__, self)
#super(B, self).__init__(1, b, c)
super(self.__class__, self).__init__(1, b, c)
class C(B):
def __init__(self, c):
#super(C, self).__init__(2, c)
super(self.__class__, self).__init__(2, c)
C(3)
No código acima, o comentado __init__
As chamadas parecem ser a maneira "inteligente" comumente aceita para fazer a inicialização da super classe. No entanto, no caso de a hierarquia de classe mudar, eu tenho usado a forma não dominada, até recentemente.
Parece que na chamada para o super construtor para B
Na hierarquia acima, isso B.__init__
é chamado de novo, self.__class__
é na verdade C
, não B
Como eu sempre assumi.
Existe alguma maneira no Python-2.x que eu possa manter o MRO adequado (com relação à inicialização de todas as classes dos pais na ordem correta) ao chamar super construtores, sem nomear a classe atual (o B
in super(B, self).__init__(1, b, c)
)?
Solução
Resposta curta: Não, não há como invocar implicitamente o direito __init__
Com os argumentos corretos da classe pai certa no Python 2.x.
Aliás, o código como mostrado aqui está incorreto: se você usar super ().__init__
, então todas as classes em sua hierarquia devem ter a mesma assinatura em seus __init__
métodos. Caso contrário, seu código poderá parar de funcionar se você introduzir uma nova subclasse que usa herança múltipla.
Ver http://fuhm.net/super-harmful/ Para uma descrição mais longa do problema (com fotos).
Outras dicas
Seu código não tem nada a ver com a ordem de resolução do método. A resolução do método vem no caso de herança múltipla, o que não é o caso do seu exemplo. Seu código está simplesmente errado porque você assume que self.__class__
é realmente a mesma classe daquele em que o método é definido e isso está errado:
>>> class A(object):
... def __init__(self):
... print self.__class__
...
>>>
>>> class B(A):
... def __init__(self):
... A.__init__(self)
...
>>> B()
<class '__main__.B'>
<__main__.B object at 0x1bcfed0>
>>> A()
<class '__main__.A'>
<__main__.A object at 0x1bcff90>
>>>
Então, quando você deve ligar:
super(B, self).__init__(1, b, c)
Você está realmente ligando:
# super(self.__class__, self).__init__(1, b, c)
super(C, self).__init__(1, b, c)
EDITAR: tentando responder melhor à pergunta.
class A(object):
def __init__(self, a):
for cls in self.__class__.mro():
if cls is not object:
cls._init(self, a)
def _init(self, a):
print 'A._init'
self.a = a
class B(A):
def _init(self, a):
print 'B._init'
class C(A):
def _init(self, a):
print 'C._init'
class D(B, C):
def _init(self, a):
print 'D._init'
d = D(3)
print d.a
impressões:
D._init
B._init
C._init
A._init
3
(Uma versão modificada de padrão de modelo).
Agora, os métodos dos pais são realmente chamados implicitamente, mas tenho que concordar com o python zen onde explícito é melhor do que implícito porque o código é menor legível e o ganho é ruim. Mas tenha cuidado com tudo _init
Os métodos têm os mesmos parâmetros, você não pode esquecer completamente os pais e eu não sugiro fazê -lo.
Para herança única, uma abordagem melhor está chamando explicitamente o método dos pais, sem invocar super
. Fazendo isso você não precisa Nomeie a classe atual, mas você ainda deve se preocupar com quem é a classe dos pais.
Boas leituras são: Como fazer-pythons-super-the-right-rathing e os links sugeridos nessa questão e em particular O Super do Python é bacana, mas você não pode usá -lo
Se é provável que a hierarquia mude seja sintomas de design ruim e tenha consequências em todas as partes que estão usando esse código e não devem ser incentivadas.
Editar 2
Outro exemplo me vem em mente, mas que usa metaclasses. Biblioteca Urwid usa metaclasse Para armazenar um atributo, __super
, na aula para que você precise acessar esse atributo.
Ex:
>>> class MetaSuper(type):
... """adding .__super"""
... def __init__(cls, name, bases, d):
... super(MetaSuper, cls).__init__(name, bases, d)
... if hasattr(cls, "_%s__super" % name):
... raise AttributeError, "Class has same name as one of its super classes"
... setattr(cls, "_%s__super" % name, super(cls))
...
>>> class A:
... __metaclass__ = MetaSuper
... def __init__(self, a):
... self.a = a
... print 'A.__init__'
...
>>> class B(A):
... def __init__(self, a):
... print 'B.__init__'
... self.__super.__init__(a)
...
>>> b = B(42)
B.__init__
A.__init__
>>> b.a
42
>>>
Talvez o que você está procurando seja metaclasses?
class metawrap(type):
def __new__(mcs,name, bases, dict):
dict['bases'] = bases
return type.__new__(mcs,name,bases,dict)
class A(object):
def __init__(self):
pass
def test(self):
print "I am class A"
class B(A):
__metaclass__ = metawrap
def __init__(self):
pass
def test(self):
par = super(self.bases[0],self)
par.__thisclass__.test(self)
foo = B()
foo.test()
Impressões "Eu sou classe A"
O que o metaclass faz é substituir a criação inicial da classe B (não o objeto) e garante que o dicionário incorporado para cada objeto B agora contenha uma matriz de bases, onde você pode encontrar todas as linhas de base para B
Que eu saiba, o seguinte não é comumente feito. Mas parece funcionar.
Métodos Em uma determinada definição de classe, sempre manifestam atributos duplos de Underscore para incluir o nome da classe em que eles são definidos. Portanto, se você esconder uma referência à classe em forma de nomes em que as instâncias podem vê-la, você pode usar isso na chamada para super
.
Um exemplo escondendo as referências sobre o próprio objeto, implementando __new__
na base de base:
def mangle(cls, name):
if not name.startswith('__'):
raise ValueError('name must start with double underscore')
return '_%s%s' % (cls.__name__, name)
class ClassStasher(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
for c in cls.mro():
setattr(obj, mangle(c, '__class'), c)
return obj
class A(ClassStasher):
def __init__(self):
print 'init in A', self.__class
super(self.__class, self).__init__()
class B(A):
def __init__(self):
print 'init in B', self.__class
super(self.__class, self).__init__()
class C(A):
def __init__(self):
print 'init in C', self.__class
super(self.__class, self).__init__()
class D(B, C):
def __init__(self):
print 'init in D', self.__class
super(self.__class, self).__init__()
d = D()
print d
E, fazendo uma coisa semelhante, mas usando uma meta-classe e guardando o __class
Referências sobre os próprios objetos de classe:
class ClassStasherType(type):
def __init__(cls, name, bases, attributes):
setattr(cls, mangle(cls, '__class'), cls)
class ClassStasher(object):
__metaclass__ = ClassStasherType
class A_meta(ClassStasher):
def __init__(self):
print 'init in A_meta', self.__class
super(self.__class, self).__init__()
class B_meta(A_meta):
def __init__(self):
print 'init in B_meta', self.__class
super(self.__class, self).__init__()
class C_meta(A_meta):
def __init__(self):
print 'init in C_meta', self.__class
super(self.__class, self).__init__()
class D_meta(B_meta, C_meta):
def __init__(self):
print 'init in D_meta', self.__class
super(self.__class, self).__init__()
d = D_meta()
print d
Executando tudo isso juntos, como um arquivo de origem:
% python /tmp/junk.py
init in D <class '__main__.D'>
init in B <class '__main__.B'>
init in C <class '__main__.C'>
init in A <class '__main__.A'>
<__main__.D object at 0x1004a4a50>
init in D_meta <class '__main__.D_meta'>
init in B_meta <class '__main__.B_meta'>
init in C_meta <class '__main__.C_meta'>
init in A_meta <class '__main__.A_meta'>
<__main__.D_meta object at 0x1004a4bd0>