tutorial - python poo pdf




Entendendo o Python super() com os métodos__init__() (5)

Esta questão já tem uma resposta aqui:

Estou tentando entender o uso de super() . Pela aparência, ambas as classes filhas podem ser criadas, muito bem.

Estou curioso para saber sobre a diferença real entre as seguintes duas classes filho.

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()

Estou tentando entender super()

O motivo pelo qual usamos super é para que as classes filho que podem estar usando herança múltipla cooperativa chame a função de classe pai próxima correta na Ordem de resolução do método (MRO).

No Python 3, podemos chamá-lo assim:

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

No Python 2, somos obrigados a usá-lo assim:

        super(ChildB, self).__init__()

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

        Base.__init__(self) # Avoid this.

Eu explico abaixo.

"Que diferença há realmente 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 principal diferença neste código é que você obtém uma camada de indireção no __init__ com super , que usa a classe atual para determinar o próximo __init__ da classe para procurar na MRO.

Eu ilustro essa diferença em uma resposta na questão canônica, Como usar 'super' em Python? , 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 alguns comportamentos de checagem e fallback, 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 o 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 super , teríamos que escrever este código manual em todos os lugares (ou recriá-lo!) Para garantir que chamamos o próximo método apropriado na Ordem de Resolução de Método!

Como o super faz isso no Python 3 sem ser explicitamente informado de qual classe e instância do método foi chamado?

Ele obtém o quadro de pilha de chamada e localiza a classe (implicitamente armazenada como uma variável livre local, __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. qual Ordem de Resolução do Método (MRO) a ser usada.

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

Críticas de outras respostas:

super () permite que você evite se referir explicitamente à classe base, o que pode ser bom. . 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 você não tiver já.

É bastante hand-wavey e não nos diz muito, mas o ponto de super não é evitar escrever a classe pai. O objetivo é garantir que o próximo método em linha na ordem de resolução do método (MRO) seja chamado. Isso se torna importante em herança múltipla.

Eu explicarei 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 chamar depois do Child:

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 o método UserDependency:

>>> 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, pois você definitivamente receberá erros quando você subclasse ChildB:

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

(Essa resposta não é inteligente ou particularmente interessante, mas apesar da crítica direta nos comentários e de mais de 17 votos negativos, o respondente persistiu em sugerir isso até que um editor gentil resolvesse seu problema.)

Explicação: Essa resposta sugeriu chamar super assim:

super(self.__class__, self).__init__()

Isso está completamente errado. super nos permite procurar o próximo pai na MRO (veja a primeira seção desta resposta) para classes filhas. Se você disser super estamos no método da instância filha, ele procurará o próximo método na linha (provavelmente este) resultando em recursão, provavelmente causando uma falha lógica (no exemplo do respondente) ou um RuntimeError quando o 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'

A principal diferença é que ChildA.__init__ chamará incondicionalmente Base.__init__ enquanto ChildB.__init__ chamará __init__ em qualquer classe que seja o ancestral ChildB na linha de ancestrais do self (que pode ser diferente do que você espera).

Se você adicionar um ClassC que usa 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, super(ChildB, self) apontará para Mixin se self for uma ocorrência ChildC .

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

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

O super considerado super post e o vídeo de acompanhamento do pycon 2015 explicam isso muito bem.


Não há, realmente. super() examina a próxima classe na MRO (ordem de resolução do método, acessada com cls.__mro__ ) para chamar os métodos. Apenas chamando a base __init__ chama a base __init__ . Acontece que a MRO tem exatamente um item - a base. Então você está realmente fazendo exatamente a mesma coisa, mas de uma maneira melhor com super() (particularmente se você entrar em herança múltipla mais tarde).


Super não tem efeitos colaterais

Base = ChildB

Base()

funciona como esperado

Base = ChildA

Base()

entra em recursão infinita.


super() permite que você evite se referir explicitamente à classe base, o que pode ser bom. 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 você não tiver já.

Note que a sintaxe mudou no Python 3.0 : você pode apenas dizer super().__init__() invés de super(ChildB, self).__init__() qual IMO é um pouco melhor. Os documentos padrão também se referem a um guia para usar super (), que é bastante explicativo.





super