maneira correta de usar super (passagem de argumento)

99

Então eu estava seguindo Super Considered Harmful do Python e fui testar seus exemplos.

No entanto, o Exemplo 1-3 , que deveria mostrar a maneira correta de chamar superao lidar com __init__métodos que esperam argumentos diferentes, não funciona.

Isso é o que eu recebo:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Parece que por objectsi só viola uma das melhores práticas mencionadas no documento, que é os métodos que o uso superdeve aceitar *argse **kwargs.

Obviamente, o Sr. Knight esperava que seus exemplos funcionassem, então isso é algo que foi alterado nas versões recentes do Python? Verifiquei 2.6 e 2.7 e falha em ambos.

Então, qual é a maneira correta de lidar com esse problema?

cha0site
fonte
2
Minha maneira preferida é: Hierarquias de herança planas e simples.
milimoose
19
Você também deve ler "Python's super () considerado super" para obter uma visão equilibrada :)
Björn Pollex
@ BjörnPollex: Obrigado! O seu link fornece uma resposta para a pergunta: Você pode escrever uma "classe root" que herda de object, e torna-se a chamada objecté __init__corretamente.
cha0site
4
Observe que o __init__on objectignora silenciosamente todos os parâmetros do Python 2.5. Isso mudou no Python 2.6.
Wilfred Hughes
1
@Wilfred: Ei, obrigado por responder à pergunta real! Agora eu sei porque o ensaio está desatualizado!
cha0site

Respostas:

103

Às vezes, duas classes podem ter alguns nomes de parâmetros em comum. Nesse caso, você não pode **kwargsretirar os pares de valores-chave ou removê-los *args. Em vez disso, você pode definir uma Baseclasse que object, ao contrário , absorve / ignora argumentos:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

rendimentos

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Observe que, para que isso funcione, Basedeve ser a penúltima turma do MRO.

unutbu
fonte
4
Não deveria Baseligar super(Base, self).__init__()?
cha0site
4
Para que isso funcione, Basedeve vir no final do MRO (exceto para object). Ligar object.__init__não faz nada, então não há problema em não ligar super(Base, self).__init__(). Na verdade, acho que pode ser mais claro não incluir, super(Base, self).__init__()para esclarecer, o ponto que Baseé o fim da linha.
unutbu
25

Se você vai ter muita herança (esse é o caso aqui), sugiro que você passe todos os parâmetros usando **kwargs, e então popeles logo após usá-los (a menos que você precise deles nas classes superiores).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

Essa é a maneira mais simples de resolver esse tipo de problema.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
fonte
6

Conforme explicado no super () do Python, considerado super , uma maneira é fazer com que a classe coma os argumentos de que necessita e passe o resto adiante. Assim, quando a cadeia de chamada chega object, todos os argumentos foram comidos e object.__init__serão chamados sem argumentos (como esperado). Portanto, seu código deve ser assim:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Björn Pollex
fonte
Então, sobre o quê E(arg = 10)? Não gosto desse método, preciso conhecer o MRO com antecedência para poder usá-lo. O outro método em que escrevo uma "classe raiz" que herda objectparece muito mais limpo.
cha0site
Este método é útil se as funções chamadas não compartilham nomes de argumentos. Eu admito que não tenho muita experiência prática com super, então esta abordagem pode ser bastante teórica.
Björn Pollex