O que 'super' faz em Python?

564

Qual é a diferença entre:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

e:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Vi superser usado bastante em classes com apenas uma herança. Percebo por que você o usaria em herança múltipla, mas não estou claro quanto às vantagens de usá-lo nesse tipo de situação.

user25785
fonte

Respostas:

309

Os benefícios da super()herança única são mínimos - principalmente, você não precisa codificar o nome da classe base em todos os métodos que usam seus métodos pai.

No entanto, é quase impossível usar herança múltipla sem super(). Isso inclui expressões comuns como mixins, interfaces, classes abstratas etc. Isso se estende ao código que posteriormente estende o seu. Se alguém mais tarde quisesse escrever uma classe que estendesse Childe um mixin, seu código não funcionaria corretamente.

John Millikin
fonte
6
você pode fornecer um exemplo com o que você quer dizer com "não funcionaria corretamente"?
Charlie Parker
319

Qual é a diferença?

SomeBaseClass.__init__(self) 

significa chamar SomeBaseClass's __init__. enquanto

super(Child, self).__init__()

significa chamar um limite __init__da classe pai que segue Childno Method Resolution Order (MRO) da instância.

Se a instância for uma subclasse de Child, pode haver um pai diferente que vem a seguir no MRO.

Explicado simplesmente

Quando você escreve uma classe, deseja que outras classes possam usá-la. super()facilita que outras classes usem a classe que você está escrevendo.

Como diz Bob Martin, uma boa arquitetura permite que você adie a tomada de decisões pelo maior tempo possível.

super() pode ativar esse tipo de arquitetura.

Quando outra classe subclasses a classe que você escreveu, também pode ser herdada de outras classes. E essas classes podem ter um __init__que vem depois disso com __init__base na ordem das classes para a resolução do método.

Sem supervocê, provavelmente, codificaria o pai da classe que você está escrevendo (como o exemplo). Isso significaria que você não chamaria o próximo __init__no MRO e, portanto, não conseguiria reutilizar o código nele.

Se você estiver escrevendo seu próprio código para uso pessoal, talvez não se importe com essa distinção. Mas se você quiser que outras pessoas usem seu código, usar superé algo que permite maior flexibilidade para os usuários do código.

Python 2 versus 3

Isso funciona no Python 2 e 3:

super(Child, self).__init__()

Isso funciona apenas no Python 3:

super().__init__()

Ele funciona sem argumentos, movendo-se no quadro da pilha e obtendo o primeiro argumento para o método (geralmente selfpara um método de instância ou clspara um método de classe - mas pode haver outros nomes) e localizando a classe (por exemplo Child) nas variáveis ​​livres ( ele é procurado com o nome __class__como uma variável de fechamento livre no método).

Prefiro demonstrar a maneira compatível de usar super, mas se você estiver usando apenas o Python 3, poderá chamá-lo sem argumentos.

Indirecionamento com compatibilidade direta

O que isso te dá? Para herança única, os exemplos da pergunta são praticamente idênticos do ponto de vista da análise estática. No entanto, usar superfornece uma camada de indireção com compatibilidade direta.

A compatibilidade direta é muito importante para desenvolvedores experientes. Você deseja que seu código continue trabalhando com alterações mínimas à medida que o altera. Quando você olha para o seu histórico de revisões, deseja ver exatamente o que mudou quando.

Você pode começar com uma herança única, mas se decidir adicionar outra classe base, precisará alterar apenas a linha com as bases - se as bases mudarem em uma classe da qual você herda (digamos que um mixin seja adicionado), você mudaria nada nesta aula. Particularmente no Python 2, obter os argumentos supere os argumentos corretos do método pode ser difícil. Se você sabe que está usando supercorretamente com herança única, isso torna a depuração menos difícil daqui para frente.

Injeção de dependência

Outras pessoas podem usar seu código e injetar os pais na resolução do método:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

Digamos que você adicione outra classe ao seu objeto e queira injetar uma classe entre Foo e Bar (para teste ou por outro motivo):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

O uso do filho não-super falha ao injetar a dependência, porque o filho que você está usando codificou o método a ser chamado após o seu:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

No entanto, a classe com o filho que usa superpode injetar corretamente a dependência:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Endereçando um comentário

Por que no mundo isso seria útil?

Python lineariza uma árvore de herança complicada por meio do algoritmo de linearização C3 para criar uma Ordem de Resolução de Método (MRO).

Queremos que os métodos sejam pesquisados nessa ordem .

Para que um método definido em um pai encontre o próximo nessa ordem sem super, ele teria que

  1. obtenha o mro do tipo da instância
  2. procure o tipo que define o método
  3. encontre o próximo tipo com o método
  4. vincule esse método e chame-o com os argumentos esperados

O UnsuperChildnão deve ter acesso a InjectMe. Por que a conclusão "Sempre evite usar super"? O que estou perdendo aqui?

O UnsuperChildque não tem acesso a InjectMe. É o UnsuperInjectorque tem acesso InjectMe- e ainda não pode chamar o método dessa classe do método que herda UnsuperChild.

As duas classes filho pretendem chamar um método com o mesmo nome que vem a seguir no MRO, que pode ser outra classe da qual não estava ciente quando foi criada.

Aquele que não supercodifica o método de seu pai - portanto, restringiu o comportamento de seu método e as subclasses não podem injetar funcionalidade na cadeia de chamadas.

Aquele com super maior flexibilidade. A cadeia de chamadas dos métodos pode ser interceptada e a funcionalidade injetada.

Você pode não precisar dessa funcionalidade, mas as subclassers do seu código podem.

Conclusão

Sempre use superpara referenciar a classe pai em vez de codificá-la.

O que você pretende é fazer referência à classe pai que é a próxima na linha, e não especificamente a que você vê o filho herdar.

O não uso superpode colocar restrições desnecessárias aos usuários do seu código.

Aaron Hall
fonte
Em C, DI é como este . código está aqui . Se eu adicionar mais uma implementação de listinterface, digamos doublylinkedlistque o aplicativo a escolha com facilidade. Posso tornar meu exemplo mais configurável, introduzindo config.txte vinculando a implementação no tempo de carregamento. Esse é o exemplo certo? Se sim, como relaciono seu código? Veja o primeiro adv do DI no wiki. Onde está configurável qualquer nova implementação? no seu código
overexchange
Uma nova implementação é criada por herança, por exemplo, onde uma das classes "Injector" herda da InjectMeclasse. Os comentários não são para discussão, no entanto, sugiro que você discuta isso com outras pessoas no bate-papo ou faça uma nova pergunta no site principal.
Aaron Hall
Ótima resposta! mas ao usar herança múltipla, há complicações com super () e __init__funções. especialmente se a assinatura de __init__variar entre as classes na hierarquia. Eu adicionei uma resposta que incide sobre este aspecto
Aviad Rozenhek
35

Eu brinquei um pouco super()e reconheci que podemos mudar a ordem de chamada.

Por exemplo, temos a próxima estrutura hierárquica:

    A
   / \
  B   C
   \ /
    D

Nesse caso, o MRO de D será (apenas para Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Vamos criar uma classe onde super()chamadas após a execução do método.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

Portanto, podemos ver que a ordem de resolução é a mesma da MRO. Mas quando chamamos super()no início do método:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

Temos uma ordem diferente, é revertida uma ordem da tupla MRO.

    A
   / 
  B  C
    /
    D 

Para leitura adicional, eu recomendaria as próximas respostas:

  1. Exemplo de linearização C3 com super (uma grande hierarquia)
  2. Alterações importantes de comportamento entre classes de estilo antigas e novas
  3. A história por dentro das novas classes de estilos
SKhalymon
fonte
Não entendo por que a ordem está mudando. A primeira parte eu entendo que DBCA porque D é a primeira classe, então quando carregar o self (B, C) acabará imprimindo B, C e somente A desde que B (A), C (A) apontou de volta para si para a final parte. Se eu seguir esse entendimento, a segunda parte não deve ser como o BCAD? Você poderia me explicar um pouco, por favor.
JJson #
Meu mal, eu não percebi que cada instância de cada classe foi iniciada com super () primeiro. Então, se for esse o caso, não deveria ser o ABCD? De alguma forma, eu entendo como o ACBD chegou, mas ainda não consegui convencer e ainda tenho um pouco de confusão. meu entendimento é que, d = D () chamada Classe D (B, C) com 2 auto-parâmetros, já que super () é iniciado primeiro, então B é chamado junto com seus atributos, então D não é impresso antes de C porque Classe D (B, C) contém 2 auto-parâmetros para que ele deve executar o segundo que é uma classe C (a), depois de executado não é mais auto-parâmetros para executado
JJson
Vai com base na definição mro .
SKHlymon # 3/18
1
então ele imprimirá C, depois imprimirá B e finalmente imprimirá D. Estou certo?
JJson
2
É muito fácil entender o segundo, desde que você obtenha o primeiro. É como uma pilha. você pressiona a impressão '' na pilha e executa super (), quando é feito o A, ele começa a imprimir coisas nessa pilha, para que a ordem seja inversa.
sol concessão
35

Tudo isso não pressupõe que a classe base seja uma classe de novo estilo?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

Não funcionará no Python 2. class Adeve ser de novo estilo, ou seja:class A(object)

mhawke
fonte
20

Ao chamar super()para resolver a versão do pai de um método de classe, método de instância ou método estático, queremos passar a classe atual cujo escopo estamos no primeiro argumento, para indicar qual escopo do pai estamos tentando resolver e como um segundo argumento, o objeto de interesse para indicar a qual objeto estamos tentando aplicar esse escopo.

Considere-se uma hierarquia de classe A, Be Conde cada classe é o progenitor do um que se lhe segue, e a, b, e crespectivas ocorrências de cada.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

Usando supercom um método static

por exemplo, usando super()de dentro do __new__()método

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Explicação:

1 - embora seja usual __new__()tomar como primeiro parâmetro uma referência à classe de chamada, ela não é implementada no Python como um método de classe, mas como um método estático. Ou seja, uma referência a uma classe deve ser passada explicitamente como o primeiro argumento ao chamar __new__()diretamente:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- Ao chamar super()para chegar à classe pai, passamos a classe filho Acomo seu primeiro argumento, depois passamos uma referência ao objeto de interesse; nesse caso, é a referência da classe que foi passada quando A.__new__(cls)foi chamada. Na maioria dos casos, também é uma referência à classe filho. Em algumas situações, pode não ser, por exemplo, no caso de heranças de várias gerações.

super(A, cls)

3- Como, como regra geral, __new__()é um método estático, super(A, cls).__new__também retornará um método estático e precisará fornecer todos os argumentos explicitamente, incluindo a referência ao objeto de interesse, neste caso cls.

super(A, cls).__new__(cls, *a, **kw)

4- fazer a mesma coisa sem super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Usando supercom um método de instância

por exemplo, usando super()de dentro__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Explicação:

1- __init__é um método de instância, o que significa que ele usa como primeiro argumento uma referência a uma instância. Quando chamada diretamente da instância, a referência é passada implicitamente, ou seja, você não precisa especificá-la:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- ao chamar super()dentro __init__(), passamos a classe filho como o primeiro argumento e o objeto de interesse como um segundo argumento, que em geral é uma referência a uma instância da classe filho.

super(A, self)

3- A chamada super(A, self)retorna um proxy que resolverá o escopo e aplicará selfcomo se fosse agora uma instância da classe pai. Vamos chamar esse proxy s. Como __init__()é um método de instância, a chamada s.__init__(...)passa implicitamente uma referência selfcomo o primeiro argumento para o pai __init__().

4- fazer o mesmo sem superprecisar passar uma referência a uma instância explicitamente para a versão do pai __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

Usando supercom um método de classe

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Explicação:

1 - Um método de classe pode ser chamado diretamente da classe e toma como primeiro parâmetro uma referência à classe.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- Ao chamar super()dentro de um método de classe para resolver a versão do pai, queremos passar a classe filho atual como o primeiro argumento para indicar qual escopo do pai estamos tentando resolver e o objeto de interesse como o segundo argumento para indicar a qual objeto queremos aplicar esse escopo, que em geral é uma referência à própria classe filho ou a uma de suas subclasses.

super(B, cls_or_subcls)

3- A chamada super(B, cls)resolve o escopo Ae aplica-a cls. Desde aalternate_constructor() é um método de classe, a chamada super(B, cls).alternate_constructor(...)passará implicitamente uma referência clscomo o primeiro argumento para Aa versão dealternate_constructor()

super(B, cls).alternate_constructor()

4- fazer o mesmo sem usar super() você precisará obter uma referência à versão não acoplada de A.alternate_constructor()(ou seja, a versão explícita da função). Simplesmente fazer isso não funcionaria:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

O exemplo acima não funcionaria porque o A.alternate_constructor()método usa uma referência implícita Acomo seu primeiro argumento. O que clsfoi aprovado aqui seria seu segundo argumento.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)
Michael Ekoka
fonte
6

Muitas ótimas respostas, mas para os aprendizes visuais: primeiro vamos explorar com argumentos para super e depois sem. exemplo de super árvore de herança

Imagine que há uma instância jackcriada a partir da classe Jack, que possui a cadeia de herança como mostrado em verde na figura. A ligar:

super(Jack, jack).method(...)

usará o MRO (Ordem de resolução de método) de jack(sua árvore de herança em uma determinada ordem) e começará a pesquisar Jack. Por que alguém pode fornecer uma classe pai? Bem, se começarmos a pesquisar a partir da instância jack, ele encontrará o método da instância, o ponto principal é encontrar o método dos pais.

Se alguém não fornece argumentos para super, é como o primeiro argumento passado é a classe de self, e o segundo argumento passado é self. Estes são calculados automaticamente para você no Python3.

No entanto, digamos que não queremos usar Jacko método, em vez de repassar Jack, poderíamos repassar Jenpara começar a procurar o método a partir de Jen.

Ele pesquisa uma camada de cada vez (largura, não profundidade), por exemplo, se Adame Sueambas tiverem o método necessário, a primeira Sueserá encontrada primeiro.

Se Caine Sueambos tivessem o método necessário, Caino método de seria chamado primeiro. Isso corresponde no código a:

Class Jen(Cain, Sue):

O MRO é da esquerda para a direita.

run_the_race
fonte
2

algumas ótimas respostas aqui, mas elas não abordam como usar super()no caso em que diferentes classes na hierarquia têm assinaturas diferentes ... especialmente no caso de__init__

Para responder a essa parte e poder usar efetivamente, super()sugiro ler minha resposta super () e alterar a assinatura dos métodos cooperativos .

aqui está apenas a solução para este cenário:

  1. as classes de nível superior em sua hierarquia devem herdar de uma classe personalizada como SuperObject:
  2. se as classes puderem aceitar argumentos diferentes, sempre passe todos os argumentos recebidos para a super função como argumentos de palavra-chave e sempre aceite **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

exemplo de uso:

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

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

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

resultado:

E name=python age=28
C age=28
A
D name=python
B
SuperObject
Aviad Rozenhek
fonte
0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Isso é bastante fácil de entender.

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

Ok, o que acontece agora se você usar super(Child,self)?

Quando uma instância filho é criada, sua MRO (ordem de resolução de método) fica na ordem de (filho, SomeBaseClass, objeto) com base na herança. (suponha que SomeBaseClass não tenha outros pais, exceto o objeto padrão)

Ao passar Child, self, superpesquisa no MRO da selfinstância e retorna o objeto proxy ao lado de Child, nesse caso é SomeBaseClass, esse objeto chama o __init__método de SomeBaseClass. Em outras palavras, se for super(SomeBaseClass,self), o objeto proxy que superretornar seriaobject

Para herança múltipla, o MRO pode conter muitas classes, portanto, basicamente, supervocê decide onde deseja iniciar a pesquisa no MRO.

dorisxx
fonte
0

Considere o seguinte código:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Se alterar o construtor de Ypara X.__init__, você obterá:

X
Y
Q

Mas super(Y, self).__init__(), usando , você obterá:

X
P
Y
Q

E Pou Qpode até estar envolvido em outro arquivo que você não conhece quando escreve Xe Y. Então, basicamente, você não saberá o que super(Child, self)fará referência quando estiver escrevendo class Y(X), mesmo a assinatura de Y é tão simples quanto Y(X). É por isso que super poderia ser uma escolha melhor.

tsh
fonte