Pergunta

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)

Eu vi super sendo usada muito em aulas com apenas herança simples. Eu posso ver por que você iria usá-la em herança múltipla, mas estou claro sobre o que as vantagens são de usá-lo neste tipo de situação.

Foi útil?

Solução

Os benefícios de super() em single-herança são mínimas - na maior parte, você não tem que codificar o nome da classe base em cada método que utiliza os seus métodos de pais

.

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

Outras dicas

Qual é a diferença?

SomeBaseClass.__init__(self) 

meios para chamar SomeBaseClass de __init__. enquanto

super(Child, self).__init__()

meios para chamar um __init__ ligado da classe pai que segue Child na instância Método resolução Order (MRO).

Se a instância é uma subclasse da criança, pode haver um pai diferente que vem a seguir na MRO.

explicada simplesmente

Quando você escrever uma classe, você quer outras classes para ser capaz de usá-lo. super() torna mais fácil para outras classes usar a classe que você está escrevendo.

Como Bob Martin diz, uma boa arquitetura permite adiar a tomada de decisões tanto tempo quanto possível.

super() pode permitir esse tipo de arquitetura.

Quando outra classe subclasses da classe que você escreveu, ele também poderia ser herdar de outras classes. E essas classes poderia ter um __init__ que vem depois desta __init__ baseada na ordenação das classes para a resolução de método.

Sem super você provavelmente iria codificar o pai da classe que você está escrevendo (como o exemplo faz). Isto significa que você não chamaria a próxima __init__ na MRO, e você, portanto, não iria começar a reutilizar o código nele.

Se você estiver escrevendo seu próprio código para uso pessoal, você pode não se preocupam com esta distinção. Mas se você quer que os outros a usar seu código, usando super é uma coisa que permite uma maior flexibilidade para os usuários do código.

Python 2 versus 3

Isso funciona em Python 2 e 3:

super(Child, self).__init__()

Isso só funciona em Python 3:

super().__init__()

Ele funciona sem argumentos, movendo-se no quadro de pilha e obtendo o primeiro argumento para o método (geralmente self para um método de instância ou cls para um método de classe -, mas poderia ser outros nomes) e encontrar a classe (por exemplo Child ) nas variáveis ??livres (isto é pesquisado com o __class__ nome de uma variável de fecho livre no método).

Eu prefiro demonstrar a maneira cross-compatível de usar super, mas se você estiver usando apenas Python 3, você pode chamá-lo sem argumentos.

Indireção com Atacante compatibilidade

O que te deu? Para herança simples, os exemplos da pergunta são praticamente idênticas a partir de um ponto de vista de análise estática. No entanto, usando super dá-lhe uma camada de engano com compatibilidade para a frente.

compatibilidade para a frente é muito importante para desenvolvedores experientes. Você quer que seu código para continuar a trabalhar com alterações mínimas como você alterá-lo. Quando você olhar para o seu histórico de revisão, que você quer ver precisamente o que mudou quando.

Você pode começar com herança única, mas se você decidir adicionar outra classe base, você só tem que mudar a linha com as bases - se as bases mudar em uma classe que herda de (dizer um mixin é adicionado) você 'd mudar nada nesta classe. Particularmente em Python 2, ficando os argumentos para super e os argumentos de métodos corretos direito pode ser difícil. Se você sabe que você está usando super corretamente com herança única, que torna a depuração mais fácil daqui para frente.

Dependency Injection

Outras pessoas podem usar seu código e os pais injetar na resolução 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__()

Say você adicionar outra classe ao seu objeto, e quer injetar uma classe entre Foo e Bar (para testar ou outra razão):

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

Usando a criança un super falhar para injetar a dependência porque a criança que você está usando tem codificado o método a ser chamado após a sua própria:

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

No entanto, a classe com a criança que usa super pode injetar corretamente a dependência:

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

Dirigindo um comentário

Por que no mundo que isso seria útil?

Python lineariza uma árvore de herança complicada através da C3 linearização algoritmo para criar uma ordem de resolução Method ( MRO).

Queremos métodos para ser olhou para cima nessa ordem .

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

  1. obter a mro de tipo da instância
  2. olhar para o tipo que define o método
  3. encontrar o próximo tipo com o método
  4. ligamento esse método e chamá-lo com os argumentos esperados

O UnsuperChild não devem ter acesso a InjectMe. Por que não é a conclusão "Sempre evitar o uso super"? O que estou ausente aqui?

O UnsuperChild não tem acesso a InjectMe. É o UnsuperInjector que tem acesso a InjectMe - e ainda não pode chamar método que de classe a partir do método que ele herda de UnsuperChild.

Ambas as classes para crianças pretende chamar um método com o mesmo nome que vem a seguir na OPR, que pode ser outro classe que ele não estava ciente de quando foi criado.

A uma sem super rígido-códigos seu método de pai - portanto, restringiu o comportamento do seu método, e subclasses podem não funcionalidade injectar na cadeia de chamada

.

A única com super tem maior flexibilidade. A cadeia de chamada para os métodos pode ser interceptado e funcionalidade injectado.

Você pode não precisar essa funcionalidade, mas subclassers de seu código pode.

Conclusão

Sempre uso super para fazer referência à classe pai em vez de codificar-lo.

O que você pretende é fazer referência a classe pai que fica ao lado-in-line, não especificamente o que você vê a criança herdar.

Não usar super pode colocar restrições desnecessárias sobre os usuários do seu código.

não tudo isso assumir que a classe base é uma classe 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 funciona em Python 2. class A deve ser novo estilo, ou seja: class A(object)

Eu tinha jogado um pouco com super(), e tinha reconhecido que podemos mudar a ordem de chamada.

Por exemplo, temos seguinte estrutura hierárquica:

    A
   / \
  B   C
   \ /
    D

Neste caso 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 as chamadas super() 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

Assim, podemos ver que a ordem de resolução é o mesmo que em 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 é reverteu uma ordem do tuple MRO.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

Para uma leitura adicional Eu recomendaria próximos respostas:

  1. C3 linearização exemplo, com super (um grande hierarquia)
  2. mudanças de comportamento importantes entre classes de estilo antigos e novos
  3. a história por dentro sobre Novo- Classes estilo

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

Considere um A hierarquia de classes, B e C onde cada classe é o pai daquele que se lhe segue, e a, b e c respectivas instâ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 super com um staticmethod

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

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- mesmo que seja usual para __new__() tomar como seu primeiro parâmetro uma referência para a classe chamada, é não implementado em Python como um classmethod, mas sim um staticmethod. Ou seja, uma referência a uma classe tem que ser passado 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 o A classe filho como seu primeiro argumento, então nós passamos uma referência ao objeto de interesse, neste caso, é a referência de classe que foi passado quando A.__new__(cls) foi chamado . Na maioria dos casos, também passa a ser uma referência para a classe filha. Em algumas situações pode não ser, por exemplo, no caso de várias heranças geração.

super(A, cls)

3 desde como um __new__() regra geral é um staticmethod, super(A, cls).__new__ também retornará um staticmethod e necessidades a serem fornecidos todos os argumentos explicitamente, incluindo a referência ao objeto de insterest, neste caso cls.

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

4 fazendo 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 super com 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 tem como primeiro argumento uma referência a uma instância. Quando chamado diretamente a partir da instância, a referência é passado implicitamente, isto é você não precisa especificá-lo:

# 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 criança como o primeiro argumento e o objecto de interesse como um segundo argumento, que em geral é uma referência a uma instância da classe criança.

super(A, self)

3 A super(A, self) chamada retorna um proxy que irá resolver o alcance e aplicá-la a self como se fosse agora uma instância da classe pai. Vamos chamada que s proxy. Desde __init__() é um método de instância da s.__init__(...) chamada passará implicitamente uma referência de self como o primeiro argumento para __init__() do pai.

4 a fazer o mesmo, sem super precisamos passar uma referência a uma instância explicitamente a versão do pai de __init__().

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

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

Usando super com um classmethod

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- A classmethod pode ser chamado a partir da classe diretamente e toma como seu primeiro parâmetro uma referência para a 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 classmethod para resolver a versão de seu pai dela, queremos passar a classe criança atual como o primeiro argumento para indicar qual dos pais âmbito que estamos a tentar resolver a, e o objeto de interesse como o segundo argumento para indicar qual objeto queremos aplicar esse escopo para, que em geral é uma referência para a classe criançaem si ou uma de suas subclasses.

super(B, cls_or_subcls)

3 As resoluções chamada super(B, cls) ao âmbito A e aplica-a cls. Desde alternate_constructor() é um classmethod o super(B, cls).alternate_constructor(...) chamada passará implicitamente uma referência de cls como o primeiro argumento para a versão de A de alternate_constructor()

super(B, cls).alternate_constructor()

4 a fazer o mesmo sem usar super() você precisaria para obter uma referência para o não ligado versão do A.alternate_constructor() (ou seja, a versão explícita da função). Simplesmente fazendo isso não iria funcionar:

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 código acima não funciona porque o método A.alternate_constructor() leva uma referência implícita ao A como seu primeiro argumento. O cls sendo passado 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)
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Este é 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 Criança é criado, seu MRO (Método resolução Order) é da ordem de (Child, SomeBaseClass, objeto) com base na herança. (Assumir que SomeBaseClass não tem outros pais, exceto para o objeto padrão)

Ao passar Child, self, pesquisas super na MRO da instância self, e devolver o objeto proxy próxima da criança, neste caso, de SomeBaseClass, este objeto, em seguida, chama o método __init__ de SomeBaseClass. Em outras palavras, se é super(SomeBaseClass,self), o objeto proxy que super retornos seria object

Para vários herança, o MRO poderia conter muitas classes, então basicamente super permite que você decida onde você quer começar a pesquisar na MRO.

algumas grandes respostas aqui, mas eles não abordar 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 de ser capaz de usar eficazmente super() i sugiro ler a minha resposta super () e alterar a assinatura de métodos cooperativos .

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

  1. as classes de nível superior na hierarquia deve herdar de uma classe personalizada como SuperObject:
  2. Se as classes podem ter argumentos diferentes, sempre passar todos os argumentos que você recebeu para a função de super como argumentos, e, sempre aceitar **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)

saída:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

Muitas grandes respostas, mas para aprendizes visuais: Em primeiro lugar vamos explorar com argumentos para super, e em seguida, sem. super exemplo árvore de herança

Imagine que theres um jack instância criada a partir da Jack classe, que tem a cadeia de herança, como mostrado em verde na imagem. Chamar:

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

usará o MRO (Método resolução Order) de jack (sua árvore de herança em uma determinada ordem), e começa a procurar pela Jack. Por que alguém pode fornecer uma classe pai? Bem, se começar a procurar a partir da instância jack, ele iria encontrar o método de instância, toda a questão é encontrar o seu método pais.

Se a pessoa não fornecer argumentos para super, é como o primeiro argumento passado é a classe de self, eo segundo argumento passado é self. Estes são auto-calculado para você em Python3.

No entanto dizer que não quer usar o método de Jack, em vez de passar em Jack, poderíamos de aprovada em Jen para iniciar a busca para cima para o método de Jen.

Ele procura uma camada de cada vez (largura não profundidade), por exemplo se Adam e Sue ambos têm o método necessário, o de Sue será encontrado primeiro.

Se Cain e Sue ambos tinham o método necessário, o método de Cain seria chamado em primeiro lugar. Isto corresponde em código para:

Class Jen(Cain, Sue):

MRO é da esquerda para a direita.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top