Skip to content

    Chapter 28, Intermediate

    Inheritance and polymorphism

    Inheritance reuses behavior and polymorphism allows treating different types the same way. I show how to use it, and especially when to prefer composition.

    Code for this chapter: intermediario/cap28_heranca.py

    Inherit and override

    A child class receives everything from the parent class and can override whatever it wants. The result is polymorphism: the same code calls the same method name and each object responds in its own way:

    intermediario/cap28_heranca.pylinhas 10 a 32
    class Animal:
        def __init__(self, nome):
            self.nome = nome
    
        def falar(self):
            return "..."
    
        def apresentar(self):
            return f"{self.nome} diz {self.falar()}"
    
    
    class Cachorro(Animal):
        def falar(self):
            return "au"
    
    
    class Gato(Animal):
        def falar(self):
            return "miau"
    
    
    for animal in [Cachorro("Rex"), Gato("Mia"), Animal("Ser")]:
        print(animal.apresentar())
    
    Output
    Rex diz au
    Mia diz miau
    Ser diz ...
    

    The apresentar is written once and works for all, because it calls self.falar(), and the self decides which version to run.

    super(): reuse the parent

    When the child needs to extend the parent's behavior, instead of replacing it, it calls the parent with super():

    intermediario/cap28_heranca.pylinhas 37 a 56
    class Funcionario:
        def __init__(self, nome, salario):
            self.nome = nome
            self.salario = salario
    
        def bonus(self):
            return self.salario * 0.1
    
    
    class Gerente(Funcionario):
        def __init__(self, nome, salario, equipe):
            super().__init__(nome, salario)
            self.equipe = equipe
    
        def bonus(self):
            return super().bonus() + 100 * len(self.equipe)
    
    
    g = Gerente("Ana", 5000, ["Bia", "Caio"])
    print(g.bonus())
    
    Output
    700.0
    

    Forgetting the super().__init__(...) is a classic mistake: the parent never initializes its attributes, and the error only appears later.

    Hierarchies and the search order

    Inheritance can have multiple levels. The order in which Python looks for a method is called MRO (method resolution order) and can be consulted:

    intermediario/cap28_heranca.pylinhas 61 a 74
    class A:
        pass
    
    
    class B(A):
        pass
    
    
    class C(B):
        pass
    
    
    print(issubclass(C, A), isinstance(C(), B))
    print([k.__name__ for k in C.__mro__])
    
    Output
    True True
    ['C', 'B', 'A', 'object']
    

    Multiple inheritance (class D(B, C)) exists and works, but it is a source of complexity. I address it in more detail at the advanced level.

    Duck typing

    Python does not require a hierarchy for polymorphism to work. If the object has the method you call, it works: "if it walks like a duck and quacks like a duck, it is a duck":

    intermediario/cap28_heranca.pylinhas 79 a 90
    class Pato:
        def falar(self):
            return "quack"
    
    
    class Robo:
        def falar(self):
            return "bip"
    
    
    for coisa in (Pato(), Robo()):
        print(coisa.falar())
    
    Output
    quack
    bip
    

    Composition before inheritance

    Inheritance means "is a". Composition means "has a". A Carro is a vehicle, but has an engine. I only inherit when the "is a" relationship is true and the child can be used anywhere the parent is expected. For everything else, I compose:

    intermediario/cap28_heranca.pylinhas 95 a 108
    class Motor:
        def ligar(self):
            return "motor ligado"
    
    
    class Carro:
        def __init__(self):
            self.motor = Motor()
    
        def partir(self):
            return self.motor.ligar()
    
    
    print(Carro().partir())
    
    Output
    motor ligado
    

    Why I prefer composition

    Inheritance couples the child to the internal details of the parent: changing the parent can break all children. Composition only couples to the public interface. When in doubt, start with composition. You can evolve to inheritance later, but undoing a hierarchy is costly.

    Exercise 1

    Geometric shapes

    Create Forma with the method area() that raises NotImplementedError, and the subclasses Retangulo(largura, altura) and Circulo(raio). Write area_total(formas).