2.22. Herança¶
A herança permite que uma classe estenda outra classe – começa com todos os seus métodos e atributos, e depois adiciona ou substitui o que é diferente. A original chama-se base (ou pai); a extensão chama-se subclasse.
Square reutiliza todos os métodos de Shape e substitui os que precisa de especializar.¶
2.22.1. Definir uma subclasse¶
A classe base vai entre parênteses na linha class:
class Shape:
def __init__(self, name):
self.name = name
def describe(self):
return self.name + " with area " + str(self.area())
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side
def area(self):
return self.side * self.side
s = Square(4)
print(s.describe())
Resultado:
square with area 16
Square herda describe de Shape sem alterações e substitui area para devolver um valor real. O describe da base ainda funciona porque chama self.area() – que resolve para a substituição em tempo de execução.
2.22.2. super()¶
super() devolve um proxy que permite que um método chame a versão da classe base de um método. O uso mais comum é chamar o __init__ da base a partir de um __init__ de subclasse para que a base possa inicializar os seus próprios atributos:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Esquecer super().__init__ é uma fonte comum de erros: os atributos que a base teria definido nunca são definidos, e os métodos herdados que dependem deles falham mais tarde.
2.22.2.1. A classe base implícita¶
Toda a classe herda implicitamente de object, a raiz da hierarquia de tipos do Python. Métodos como __repr__, __eq__, e os mecanismos por trás do acesso a atributos vêm todos daí, mesmo quando uma classe não declara nenhuma base. Escrever class Foo(object): explicitamente e escrever class Foo: são equivalentes no Python moderno; a última é a forma convencional.
2.22.3. Quando a herança ajuda – e quando não ajuda¶
Use herança quando uma classe é genuinamente um tipo mais específico de outra, partilhando a maior parte do comportamento. O teste clássico é a verificação «é-um»: um Square é uma Shape.
Quando duas classes apenas partilham alguns auxiliares, a herança é excessiva. Recorra antes à composição: faça com que uma classe contenha uma instância da outra como atributo e use-a através desse atributo. A relação é «tem-um», não «é-um»:
class Logger:
def log(self, msg):
print("[log]", msg)
# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
def run(self):
self.log("starting")
# composition: Worker HAS-A Logger
class WorkerComposes:
def __init__(self):
self.logger = Logger()
def run(self):
self.logger.log("starting")
Ambas funcionam. A versão por composição é geralmente melhor porque:
Mantém as interfaces pequenas.
WorkerComposesexpõe apenasrunelogger.WorkerInheritstambém expõelog– os invocadores podem escreverworker.log(...)directamente, independentemente de isso ser intencional ou não.Desacopla os ciclos de vida. O logger pode ser substituído, partilhado entre trabalhadores ou construído de forma diferente por trabalhador, tudo sem tocar na classe
Worker. Uma subclasse está presa à sua base.Evita colisões de nomes de métodos. Duas bases que definem ambas
runcolidem quando uma classe tenta herdar das duas; dois atributos nunca colidem.
Uma regra prática:
Use herança quando a subclasse realmente é um tipo da base e todos os métodos da base fazem sentido nela –
Squareé umaShape;MemoryErroré umaException.Use composição para tudo o resto – quando apenas quer o comportamento de outra classe, não a sua identidade. A maior parte do código real usa composição muito mais do que usa herança.