Compreender o Super Python () com os métodos init () [duplicar]

esta pergunta já tem uma resposta aqui:

  • O que faz o 'super' em Python? 6 respostas
Estou a tentar entender o uso de super(). Ao que parece, ambas as classes infantis podem ser criadas, muito bem.

Estou curioso para saber qual é a diferença entre as duas seguintes aulas infantis.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()
Author: U9-Forward, 2009-02-23

7 answers

super() permite-lhe evitar referir-se explicitamente à classe base, o que pode ser agradável. Mas a principal vantagem vem com herança múltipla, onde todos os tipos de coisas divertidas podem acontecer. Veja os documentos padrão em super se ainda não o fez.

Note que a sintaxe mudou no Python 3.0 : Você pode apenas dizer super().__init__() em vez de super(ChildB, self).__init__() Qual IMO é um pouco mais agradável. Os documentos padrão também se referem a um guia para o uso de super () que é bastante explicativo.

 1463
Author: Kiv, 2018-10-11 15:46:47
Estou a tentar entender.

A razão pela qual usamos super é para que as classes de crianças que podem estar a usar a herança múltipla cooperativa chamem a função de classe seguinte correcta na ordem de resolução de métodos (MRO).

No Python 3, podemos chamar-lhe assim:
class ChildB(Base):
    def __init__(self):
        super().__init__() 

No Python 2, temos de O usar assim:

        super(ChildB, self).__init__()

Sem super, você está limitado na sua capacidade de usar múltiplos herança:

        Base.__init__(self) # Avoid this.

Eu explico mais abaixo.

Qual é a diferença neste código?:"
class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

A diferença primária neste código é que você tem uma camada de indireção no __init__ com super, que usa a classe atual para determinar a classe seguinte __init__ para olhar para cima na MRO.

Ilustro esta diferença numa resposta à pergunta canónica , Como usar 'super' em Python?, o que demonstra injeção de dependência e herança múltipla cooperativa.

Se o Python não tivesse super

Aqui está um código que é realmente equivalente a super (como é implementado em C, menos algumas verificações e comportamento de recuo, e traduzido para Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1
Escrito um pouco mais como Python nativo:
class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break
Se não tivéssemos o objecto super, teríamos de escrever este código manual em todo o lado (ou recriá-lo!) para garantir que chame o próximo método apropriado na ordem de resolução do método!

Como é que o super Faz isto no Python 3 sem ser dito explicitamente de que classe e instância do método foi chamado?

Ele recebe a pilha de chamadas, e encontra a classe (implicitamente armazenada como uma variável local livre, __class__, tornando a função de chamada um fechamento sobre a classe) e o primeiro argumento para essa função, que deve ser a instância ou classe que a informa que Método resolução ordem OPR coloc.

Uma vez que requer que o primeiro argumento para a OPR, usar super com métodos estáticos é impossível.

Críticas a outras respostas:

Super () permite-lhe evitar referir-se explicitamente à classe base, o que pode ser bom. . Mas a principal vantagem vem com herança múltipla, onde todo tipo de coisas divertidas podem acontecer. Vê os médicos normais no super, se ainda não o fizeste.

É bastante ondulado e não diz nós muito, mas o objetivo de super não é evitar escrever a classe dos pais. A questão é garantir que o próximo método, em linha com a ordem de resolução do método (OPR), seja chamado. Isso se torna importante em herança múltipla. Eu explico aqui.
class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()
E vamos criar uma dependência que queremos que nos chamem depois da criança.
class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Agora lembre-se, ChildB usa super, ChildA não:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

E UserA não chama a dependência do utilizador método:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Mas UserB, porque ChildB usa super, faz!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>
Crítica por outra resposta

Em nenhuma circunstância você deve fazer o seguinte, o que outra resposta sugere, como você definitivamente obter erros quando você subclasse ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(esta resposta não é inteligente nem particularmente interessante, mas, apesar da crítica directa nos comentários e de mais de 17 votos negativos, o respondedor persistiu em sugeri-la até que um editor gentil fixasse problema.)

Explicação: essa resposta sugeriu chamar super assim:
super(self.__class__, self).__init__()
Isto é completamente errado. ([15]} vamos procurar o próximo pai na MRO (ver a primeira secção desta resposta) para as aulas infantis. Se você disser super que estamos no método da instância infantil, então ele irá procurar o próximo método na linha (provavelmente este) resultando em recursão, provavelmente causando uma falha lógica (no exemplo do respondedor, ele faz) ou um RuntimeError Quando a profundidade de recursão é excedida.
>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
 452
Author: Aaron Hall, 2017-12-02 23:10:30

Foi observado que no Python 3.0 + pode usar

super().__init__()

Para fazer a sua chamada, que é concisa e não requer que você faça referência explícita aos nomes dos pais ou classes, o que pode ser útil. Só quero acrescentar que para o Python 2.7 ou em baixo, é possível obter este comportamento insensível ao nome escrevendo self.__class__ em vez do nome da classe, ou seja

super(self.__class__, self).__init__()
No entanto, esta pausa chama a super para qualquer classe que herde da sua classe, onde self.__class__ poderia volta para a aula infantil. Por exemplo:
class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass
Aqui tenho uma classe que é uma sub-classe de Rectangle. Dizer que eu não quero escrever um separado construtor Square porque o construtor para Rectangle é bom o suficiente, mas por alguma razão eu quero implementar um Quadrado para que eu possa implementar algum outro método.

Quando eu crio um Square Usando mSquare = Square('a', 10,10), Python chama o construtor para Rectangle Porque eu não dei Square o seu próprio construtor. No entanto, no construtor de Rectangle, a call super(self.__class__,self) vai devolver a superclasse de mSquare, por isso chama o construtor para Rectangle novamente. É assim que o ciclo infinito acontece, como foi mencionado por @S_C. neste caso, quando eu executar super(...).__init__() estou chamando o construtor para Rectangle mas como eu não lhe dou argumentos, eu vou obter um erro.

 224
Author: AnjoMan, 2017-12-05 03:31:03

O Super não tem efeitos secundários

Base = ChildB

Base()

Funciona como esperado

Base = ChildA

Base()

Entra numa recursão infinita.

 75
Author: S C, 2012-11-27 23:55:53
Só um aviso... com o Python 2.7, e eu acredito que desde que super() foi introduzido na versão 2.2, você só pode chamar super() se um dos pais herda de uma classe que eventualmente herda object (classes de estilo novo ).

Pessoalmente, quanto ao código python 2.7, eu vou continuar usando BaseClassName.__init__(self, args) até que eu realmente tenha a vantagem de usar {[[0]}.

 68
Author: rgenito, 2012-08-07 18:05:43
Na verdade, não há. {[[0]} olha para a próxima classe na ordem de resolução do método, acessada com cls.__mro__) para chamar os métodos. Ligando para a base __init__ liga para a base __init__. Por acaso, a OPR tem exactamente um item, a base. Então você está realmente fazendo exatamente a mesma coisa, mas de uma maneira mais agradável com {[[0]} (particularmente se você entra em herança múltipla mais tarde).
 47
Author: Devin Jeanpierre, 2009-02-23 00:34:57

A principal diferença é que {[1] } chamará incondicionalmente Base.__init__ Considerando que {[3] } chamará __init__ em seja qual for a classe Que Acontece ser ChildB ancestral na linha de antepassados self's (que pode diferir do que você espera).

Se adicionar um {[7] } que use herança múltipla:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base
Então ... Base não é mais o pai de ChildB para instâncias ChildC. Agora {[11] } vai apontar para Mixin SE self é uma instância ChildC.

Você inseriu Mixin entre ChildB e Base. E você pode tirar proveito disso com super()

Então, se você foi projetado suas aulas para que elas possam ser usadas em um cenário de herança múltipla cooperativa, você usa super porque você realmente não sabe quem vai ser o ancestral no tempo de execução.

O super considerado super post e pycon 2015 que acompanha o vídeo explica isto muito bem.

 23
Author: ecerulm, 2017-10-26 18:35:22