Domine `super()` em Python: Herança, MRO e Boas Práticas

Principais Pontos

  • A função `super()` em Python possibilita a invocação de métodos de classes superiores a partir de subclasses, simplificando a implementação de herança e a substituição de métodos.
  • O funcionamento da função `super()` está intrinsecamente ligado à Ordem de Resolução de Método (MRO) em Python, que estabelece a sequência na qual as classes ancestrais são consultadas na busca por métodos ou atributos.
  • A utilização de `super()` em construtores de classes é uma prática comum para inicializar atributos gerais na classe pai e atributos mais específicos na classe filha. A omissão do uso de `super()` pode ocasionar resultados inesperados, como a ausência de inicialização de atributos.

Um dos pilares do Python é seu paradigma de Programação Orientada a Objetos (POO), que possibilita a modelagem de entidades do mundo real e suas relações.

Ao lidar com classes em Python, é comum empregar a herança e sobrescrever atributos ou métodos de uma superclasse. O Python disponibiliza a função `super()` que permite acionar os métodos da superclasse a partir da subclasse.

O que é `super()` e por que ela é necessária?

Através da herança, é possível criar novas classes em Python que herdam características de classes já existentes. Também é viável substituir métodos da superclasse na subclasse, oferecendo implementações alternativas. No entanto, em vez de simplesmente substituir, pode-se desejar adicionar novas funcionalidades às já existentes. É nesse contexto que `super()` se mostra útil.

A função `super()` pode ser usada para acessar atributos da superclasse e invocar seus métodos. `Super()` é essencial na programação orientada a objetos, pois facilita a implementação de herança e a substituição de métodos.

Como a função `super()` opera?

Internamente, o funcionamento de `super()` está intimamente atrelado à Ordem de Resolução de Método (MRO) em Python, que é determinada pelo algoritmo de Linearização C3.

Veja como `super()` funciona:

  • Identificação da classe e instância atuais: Ao acionar `super()` dentro de um método de uma subclasse, Python automaticamente detecta a classe atual (a classe que contém o método que invocou `super()`) e a instância dessa classe (ou seja, `self`).
  • Determinação da superclasse: `super()` recebe dois argumentos – a classe atual e a instância – que não precisam ser passados explicitamente. Ela emprega essas informações para identificar a superclasse à qual deve delegar a chamada do método. Isso é feito através da análise da hierarquia de classes e do MRO.
  • Invocação do método na superclasse: Após a identificação da superclasse, `super()` possibilita a invocação dos seus métodos como se eles estivessem sendo chamados diretamente da subclasse. Isso permite ampliar ou substituir métodos, mantendo a implementação original da superclasse.
  • Utilizando `super()` em um construtor de classe

    O uso de `super()` em um construtor de classe é uma prática comum, já que frequentemente se deseja inicializar atributos comuns na classe pai e atributos mais específicos na classe filha.

    Para ilustrar, considere a definição de uma classe Python, `Pai`, da qual uma classe `Filho` herda:

    class Pai:
        def __init__(self, primeiro_nome, sobrenome):
            self.primeiro_nome = primeiro_nome
            self.sobrenome = sobrenome
    
    class Filho(Pai):
        def __init__(self, primeiro_nome, sobrenome, idade, hobbie):
            
            super().__init__(primeiro_nome, sobrenome)
    
            self.idade = idade
            self.hobbie = hobbie
    
        def get_info(self):
            return f"Nome do Filho: {self.primeiro_nome} {self.sobrenome}, \
    Idade do Filho: {self.idade}, Hobbie do Filho: {self.hobbie}"
    
    filho = Filho("Pius", "Effiong", 25, "Tocar Guitarra")
    
    print(filho.get_info())
    

    Dentro do construtor de `Filho`, a chamada para `super().__init__()` invoca o construtor da classe `Pai`, passando `primeiro_nome` e `sobrenome` como parâmetros. Isso garante que a classe `Pai` possa definir os atributos de nome corretamente, mesmo em um objeto `Filho`.

    Se a função `super()` não for chamada em um construtor de classe, o construtor da classe pai não será executado. Isso pode levar a resultados indesejados, como a ausência de inicializações de atributos ou a configuração incompleta do estado da classe pai:

    ...
    class Filho(Pai):
        def __init__(self, primeiro_nome, sobrenome, idade, hobbie):
            self.idade = idade
            self.hobbie = hobbie
    ...
    

    Caso se tente acionar o método `get_info` neste cenário, um `AttributeError` será gerado, pois os atributos `self.primeiro_nome` e `self.sobrenome` não foram inicializados.

    Utilizando `super()` em métodos de classe

    A função `super()` pode ser empregada em outros métodos, além dos construtores, da mesma maneira. Isso viabiliza a extensão ou a substituição do comportamento do método da superclasse.

    class Pai:
        def falar(self):
            return "Olá do Pai"
    
    class Filho(Pai):
        def falar(self):
            
            saudacao_pai = super().falar()
            return f"Olá do Filho\n{saudacao_pai}"
    
    filho = Filho()
    
    saudacao_filho = filho.falar()
    
    print(saudacao_filho)
    

    A classe `Filho` herda de `Pai` e possui seu próprio método `falar`. O método `falar` da classe `Filho` utiliza `super().falar()` para acionar o método `falar` da classe `Pai`. Isso possibilita a inclusão da mensagem da classe pai, enquanto a estende com uma mensagem específica para a classe filha.

    A omissão da função `super()` em um método que substitui outro implica que a funcionalidade presente no método da classe pai não terá efeito. Isso resulta em uma substituição total do comportamento do método, o que pode levar a um comportamento indesejado.

    Compreendendo a Ordem de Resolução do Método

    A Ordem de Resolução de Método (MRO) é a sequência na qual Python busca classes ancestrais quando um método ou atributo é acessado. O MRO auxilia Python a determinar qual método acionar quando existem múltiplas hierarquias de herança.

    class Nigeria():
        def cultura(self):
            print("Cultura da Nigéria")
    
    class Africa():
        def cultura(self):
            print("Cultura da África")
    

    Veja o que acontece quando uma instância da classe `Lagos` é criada e o método `cultura` é chamado:

  • Python inicia a busca pelo método `cultura` na própria classe `Lagos`. Caso seja encontrado, o método é acionado. Caso contrário, o processo avança para a etapa dois.
  • Se o método `cultura` não for encontrado na classe `Lagos`, Python examina as classes base na ordem em que aparecem na definição da classe. Neste exemplo, `Lagos` herda primeiro de `África` e depois de `Nigéria`. Portanto, Python procurará primeiro o método `cultura` em `África`.
  • Se o método `cultura` não for encontrado na classe `África`, Python procurará na classe `Nigéria`. Esse comportamento continua até que o final da hierarquia seja alcançado, e um erro é gerado se o método não for encontrado em nenhuma das superclasses.
  • A saída apresenta a Ordem de Resolução de Método de `Lagos`, da esquerda para a direita.

    Armadilhas comuns e práticas recomendadas

    Ao trabalhar com `super()`, é importante estar ciente de algumas armadilhas comuns para evitar.

  • Esteja atento à Ordem de Resolução do Método, particularmente em cenários de herança múltipla. Se for necessário utilizar herança múltipla complexa, é essencial familiarizar-se com o Algoritmo de Linearização C3 que Python emprega para determinar o MRO.
  • Evite dependências circulares na hierarquia de classes, que podem resultar em um comportamento imprevisível.
  • Documente seu código de forma clara, sobretudo ao utilizar `super()` em hierarquias de classes complexas, para torná-lo mais compreensível para outros desenvolvedores.
  • Utilize `super()` da maneira correta

    A função `super()` do Python é um recurso poderoso quando se trabalha com herança e substituição de métodos. A compreensão do funcionamento de `super()` e a observância das práticas recomendadas permitirão a criação de códigos mais sustentáveis e eficientes em seus projetos Python.