Herança Python: Práticas recomendadas para código reutilizável
Imagine que você está criando um sistema de software com várias funções de usuário, como alunos, professores e administradores. Essas funções compartilham atributos comuns, como nome e ID, mas também exigem funcionalidades que são exclusivas para elas. Em vez de duplicar o código, a herança permite que você defina o comportamento compartilhado em uma classe principal e o estenda em classes secundárias especializadas.
Neste artigo, exploraremos a herança Python, abordando conceitos básicos e avançados, como a substituição de métodos e a função super()
, que é uma função integrada que retorna um objeto temporário da superclasse, , para que você possa acessar seus métodos sem nomear explicitamente a classe pai. Não se preocupe se isso ainda não fizer sentido, pois aprenderemos tudo sobre isso a seguir.
Aprenda Python do zero
Noções básicas de herança em Python
A herança é um dos pilares fundamentais da programação orientada a objetos (OOP) que permite que uma classe (chamada de classe filha) derive atributos e métodos de outra classe (chamada de classe pai). Esse recurso é essencial para areutilização do código e simplifica a manutenção, facilitando a criação de programas dimensionáveis e eficientes.
Definição de classes pai e filho
Antes de prosseguir, vamos explorar a relação entre as classes pai e filha.
Classe pai
Vamos começar com a classe principal. Uma classe pai é a classe base da qual derivam as classes filhas. Ele encapsula atributos e métodos compartilhados.
Usando o Python, veja como definimos uma classe principal:
class ParentClass:
def __init__(self, attributes):
# Initialize attributes
pass
def method(self):
# Define behavior
pass
Classe infantil
Uma classe filha herda atributos e métodos da classe pai. Isso permite que você use a funcionalidade definida na classe principal. O código a seguir mostra como uma classe filha herda atributos e métodos de uma classe pai:
class ChildClass(ParentClass):
def additional_method(self):
# Define new behavior
pass
Essa sintaxe simples permite que a classe filha utilize e estenda a funcionalidade definida na classe pai.
Criando uma classe pai e uma classe filha
Vamos criar um exemplo prático com uma classe Person
como pai e uma classe Student
como filho.
Criando a classe pai
A classe Person
contém atributos compartilhados e um método para exibir informações:
# Defining the Parent Class
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def display_info(self):
return f"Name: {self.name}, ID: {self.id}"
Criando a classe filha
A classe Student
herda de Person
e adiciona um novo método study
.
# Defining the Child Class
class Student(Person):
def study(self):
return f"{self.name} is studying."
Vamos testar as classes pai e filho.
# Creating and Testing Instances
student = Student("Samuel", 102)
print(student.display_info())
print(student.study())
Name: Samuel, ID: 102
Samuel is studying.
O que está acontecendo é o seguinte:
-
A classe
Student
usa o método__init__
dePerson
para inicializarname
eid
. -
O método
study
é exclusivo da classeStudent
, ampliando sua funcionalidade. -
O método
display_info
é herdado diretamente dePerson
.
Tipos de herança em Python
A herança em Python permite que as classes herdem atributos e comportamentos de outras classes, promovendo a reutilização do código e o design limpo, conforme falamos anteriormente. Nesta seção, podemos falar sobre os diferentes tipos de herança Python, que incluem herança única, múltipla, hierárquica e híbrida como categorias separadas.
Herança única
A herança única ocorre quando uma classe filha herda de uma única classe pai, permitindo que ela estenda a funcionalidade da classe pai. Isso é útil quando um tipo de objeto compartilha propriedades comuns com uma categoria mais ampla, mas também requer atributos ou comportamentos adicionais.
O exemplo que comecei a analisar anteriormente era de herança única, mas agora vamos dar uma olhada um pouco mais de perto: Em um sistema de gestão escolar, todos os indivíduos, incluindo alunos, professores e funcionários, compartilham alguns detalhes comuns, como name
e ID
. No entanto, os alunos também têm registros acadêmicos, como notas e cursos matriculados. Usando a herança única, podemos criar uma classe Person
para atributos compartilhados e estendê-la com uma classeStudent
para detalhes acadêmicos.
Herança única. Imagem do autor
Aqui está um bom exemplo do cenário acima:
class Person:
"""Represents a general person with basic details."""
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
"""Represents a student, extending the Person class to include academic details."""
def __init__(self, name, id, grade, courses):
super().__init__(name, id)
self.grade = grade
self.courses = courses
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Courses: {', '.join(self.courses)}"
# Example usage in a school system
student = Student("Samuel", 5678, "B+", ["Math", "Physics", "Computer Science"])
print(student.get_details())
Name: Samuel, ID: 5678, Grade: B+, Courses: Math, Physics, Computer Science
A classe Student
herda o método get_details()
de Person
, mas o estende para incluir grade
e courses
. Esse é um bom exemplo de como a herança única promove o que é conhecido como código modular.
Herança múltipla
De certa forma, a herança múltipla, como uma árvore genealógica, permite que uma classe filha herde de mais de uma classe pai, combinando atributos e comportamentos de cada uma delas. Isso pode levar a possíveis conflitos, que o Python resolve usando a ordem de resolução de método (MRO).
Herança múltipla. Imagem do autor
Dê uma olhada:
class Person:
def get_details(self):
return "Details of a person."
class Athlete:
def get_skill(self):
return "Athletic skills."
class Student(Person, Athlete):
pass
# Example usage
student = Student()
print(student.get_details())
print(student.get_skill())
Details of a person.
Athletic skills.
Vemos que a classe Student
herdou atributos e métodos de Person
e Athlete
. Sem nenhum esforço adicional, a classe Student
tem acesso ao método get_details()
da classe pai Person
e ao método get_skill()
da classe pai Athlete
. Estamos combinando efetivamente a funcionalidade de várias fontes.
Entretanto, a herança de várias classes pode gerar conflitos. E se ambas as classes pai definirem um método ou atributo com o mesmo nome? Mencionei algo sobre a ordem de resolução de métodos anteriormente, mas deixe-me falar um pouco mais sobre isso. Ordem de resolução de métodos determina a ordem em que as classes são pesquisadas em busca de métodos e atributos. O MRO segue uma abordagem de profundidade primeiro, da esquerda para a direita.
Você pode visualizar o MRO de uma classe usando o atributo __mro__
ou o método mro()
:
print(Student.__mro__)
(<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.Athlete'>, <class 'object'>)
Herança multinível, hierárquica e híbrida
O Python também oferece suporte a estruturas de herança mais complexas. Mostrarei essas ideias mais complexas usando o mesmo exemplo.
Herança em vários níveis
A herança multinível ocorre quando uma classe filha herda de outra classe filha, e essa classe filha herda de uma classe pai. Isso cria uma cadeia de herança.
Herança em vários níveis. Imagem do autor
Aqui está um bom exemplo:
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
class GraduateStudent(Student):
def __init__(self, name, id, grade, thesis_title):
super().__init__(name, id, grade)
self.thesis_title = thesis_title
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Thesis: {self.thesis_title}"
# Example usage
grad_student = GraduateStudent("Charlie", 91011, "A", "AI in Healthcare")
print(grad_student.get_details())
Name: Charlie, ID: 91011, Grade: A, Thesis: AI in Healthcare
Aqui, cada classe da cadeia acrescenta algo novo: Person
gerencia nomes e IDs, Student
inclui notas e GraduateStudent
apresenta uma tese. Graças ao super().__init__()
, reutilizamos a lógica de inicialização sem duplicar o código. É eficiente, organizado e garante que todos os níveis da "escada de herança", como eu penso, funcionem.
Herança hierárquica
Na herança hierárquica, várias classes filhas herdam de uma única classe mãe, permitindo o comportamento compartilhado entre subclasses com atributos exclusivos.
Herança hierárquica. Imagem do autor
Vamos dar uma olhada em um bom exemplo juntos:
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
class Teacher(Person):
def __init__(self, name, id, subject):
super().__init__(name, id)
self.subject = subject
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Subject: {self.subject}"
# Example usage
student = Student("Samuel", 5678, "B+")
teacher = Teacher("Dr. Smith", 1234, "Math")
print(student.get_details())
print(teacher.get_details())
Name: Samuel, ID: 5678, Grade: B+
Dr. Smith, ID: 1234, Subject: Math
Aqui, a classe Person
serve como base, oferecendo atributos e métodos comuns (name
, id
e get_details
). As classes Student
e Teacher
estendem essa funcionalidade adicionando suas propriedades exclusivas (grade
e subject
) e personalizando o método get_details
para refletir seus contextos específicos.
Com essa abordagem, a funcionalidade compartilhada permanece em um único lugar (a classe Person
), enquanto o comportamento especializado é perfeitamente encapsulado nas subclasses.
Herança híbrida
A herança híbrida combina vários tipos de herança, como a herança multinível ou múltipla, para modelar relacionamentos mais complexos.
Herança híbrida. Imagem do autor
Vamos dar uma olhada em um exemplo que mostra a complexidade da herança híbrida.
# Base class
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
# Intermediate class inheriting from the base class
class Employee(Person):
def __init__(self, name, id, position):
super().__init__(name, id)
self.position = position
def get_position(self):
return f"Position: {self.position}"
# Another independent base class
class Athlete:
def __init__(self, sport):
self.sport = sport
def get_sport(self):
return f"Sport: {self.sport}"
# Derived class combining Employee and Athlete
class Student(Employee, Athlete):
def __init__(self, name, id, position, grade, sport):
Employee.__init__(self, name, id, position)
Athlete.__init__(self, sport)
self.grade = grade
def get_grade(self):
return f"Grade: {self.grade}"
# Example usage
student = Student("Samuel", 1234, "Intern", "A", "Soccer")
print(student.get_details()) # From Person
print(student.get_position()) # From Employee
print(student.get_grade()) # From Student
print(student.get_sport()) # From Athlete
Name: Samuel, ID: 1234
Position: Intern
Grade: A
Sport: Soccer
Neste exemplo, a classe Student
demonstra a herança híbrida ao herdar atributos e métodos de Employee
(que por sua vez herda de Person
) e Athlete
. Isso combinaa herança hierárquica de (em que Employee
herda de Person
) e a herança múltipla (em que Student
herda de Employee
e Athlete
).
Benefícios da herança em Python
Agora, é hora de ver os pontos fortes e fracos:
Benefícios da herança
-
Reutilização: Com a herança, você pode escrever o código uma vez na classe principal e reutilizá-lo nas classes secundárias. Usando o exemplo, tanto
FullTimeEmployee
quantoContractor
podem herdar um métodoget_details()
da classe paiEmployee
. -
Simplicidade: A herança modela os relacionamentos de forma clara. Um bom exemplo é a classe
FullTimeEmployee
que "é um" tipo da classe paiEmployee
. -
Escalabilidade: Ele também adiciona novos recursos ou classes secundárias sem afetar o código existente. Por exemplo, podemos adicionar facilmente uma nova classe
Intern
como uma classe filha.
Possíveis limitações da herança
-
Complexidade: Isso não é surpreendente, mas muitos níveis de herança podem tornar o código difícil de acompanhar. Por exemplo, se um
Employee
tiver muitas classes filhas, comoManager
,Engineer
,Intern
, etc., você poderá ficar confuso. -
Dependência: As alterações em uma classe principal podem afetar involuntariamente todas as subclasses. Se você modificar
Employee
, por exemplo, poderá quebrarFullTimeEmployee
ouContractor
. -
Uso indevido: O uso da herança quando ela não é a melhor opção pode complicar os projetos. Você não gostaria de criar uma solução em que
Car
herdasse deBoat
apenas para reutilizarmove()
. O relacionamento não faz sentido.
Técnicas avançadas de herança em Python
Agora que já exploramos os conceitos básicos de herança, vamos examinar algumas técnicas avançadas. Essas técnicas, como substituição de métodos, super()
, classes de base abstratas e polimorfismo, aumentam a flexibilidade do código e permitem padrões de design mais sofisticados.
Substituição de métodos em python
A substituição de método permite que uma classe secundária forneça uma implementação específica para um método já definido em sua classe principal. Isso é útil quando o comportamento herdado não atende totalmente aos requisitos da classe filha.
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
# Overriding the get_details method
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
# Example usage
student = Student("Samuel", 1234, "A")
print(student.get_details())
Name: Samuel, ID: 1234, Grade: A
Aqui, a classe Student
substitui o método get_details()
da classe Person
para fornecer suas próprias implementações específicas. Isso permite que a classe filha tenha seu próprio comportamento e, ao mesmo tempo, siga o mesmo nome de método.
Então, por que substituímos? Substituímos porque queremos personalizar o comportamento herdado e também porque queremos adaptar a funcionalidade de um método pai aos requisitos exclusivos de uma classe filha.
Usando super() para inicialização do pai
A função super()
é usada para chamar métodos na classe pai a partir da classe filha. Isso é particularmente útil quando você deseja estender ou modificar a funcionalidade de um método da classe principal, como o método do construtor __Init__()
.
Então, por que usamos a função super()
? Usamos a função super porque queremos chamar e inicializar o construtor da classe principal e também porque queremos evitar nomear explicitamente a classe principal. Isso é útil, especialmente em casos de herança múltipla.
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
class Student(Person):
def __init__(self, name, id, grade):
# Using super() to initialize the parent class
super().__init__(name, id)
self.grade = grade
# Example usage
student = Student("Samuel", 5678, "B+")
print(student.name)
print(student.id)
print(student.grade)
Samuel
5678
B+
Aqui, a classe Student
usa super().__init__(name, id)
para chamar o método __init__
da classe pai Person
, de modo que não é necessário repetir o código para inicializar os atributos name
e id
. A classe filha introduz, então, um atributo grade
, que é específico da classe Student
.
Classes básicas abstratas (ABCs)
Uma classe base abstrata (ABC) é uma classe que não pode ser usada diretamente para criar objetos. Seu objetivo é definir um conjunto comum de métodos que outras classes devem implementar. Portanto, os ABCs são úteis quando você deseja garantir que determinados métodos estejam sempre presentes nas classes filhas.
from abc import ABC, abstractmethod
class Person(ABC):
@abstractmethod
def get_details(self):
pass
class Student(Person):
def __init__(self, name, id, grade):
self.name = name
self.id = id
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
# Example usage
student = Student("Hamilton", 7890, "A-")
print(student.get_details())
Name: Hamilton, ID: 7890, Grade: A-
A classe Person
aqui é uma classe abstrata que exige que qualquer classe filha implemente o método get_details()
. Esse método está sendo implementado posteriormente pela classe filha, Student
.
Polimorfismo
Polimorfismo significa muitas formas. Em Python, isso permite que classes diferentes usem o mesmo nome de método, mas cada uma pode implementar esse método de uma maneira diferente.
O polimorfismo nos ajuda a escrever códigos que podem funcionar com objetos de classes diferentes, mesmo que essas classes tenham comportamentos diferentes:
class Person:
def get_details(self):
return "Details of a person."
class Student(Person):
def get_details(self):
return "Details of a student."
class Teacher(Person):
def get_details(self):
return "Details of a teacher."
# Example usage
def print_details(person):
print(person.get_details())
student = Student()
teacher = Teacher()
print_details(student)
print_details(teacher)
Details of a student.
Details of a teacher.
Neste exemplo, a função print_details()
pode aceitar qualquer objeto do tipo Person
, mas chamará o método get_details()
apropriado com base no fato de o objeto ser um Student
ou um Teacher
.
Erros comuns e práticas recomendadas
Embora a herança seja poderosa, é fácil fazer mau uso dela. Compartilharei algumas ideias para ajudar você a tirar o máximo proveito delas.
Evitando surpresas com métodos substituídos
Quando uma classe filha substitui um método de sua classe mãe, o comportamento pode mudar.
Por exemplo, se a classe principal Employee
tiver um método calculate_pay()
e a classe secundária Manager
o substituir sem considerar todos os cenários, isso poderá produzir cálculos de pagamento incorretos.
A prática recomendada, nesse caso, é sempre testar minuciosamente os métodos substituídos e documentar o comportamento deles.
Escolha entre herança e composição
Sei que este artigo trata de herança, mas nem sempre essa é a abordagem correta. Às vezes, a composição, em que você cria classes combinando objetos em vez de estendê-los, pode ser mais adequada ao que você está fazendo.
Para distinguir as diferenças da maneira mais básica, você deve pensar assim:
-
A herança refere-se a relacionamentos "Is-a". Por exemplo, um
Manager
é umEmployee
. -
Composição refere-se a relacionamentos "Has-a". Por exemplo, um
Car
tem umEngine
.
Então, como você sabe quando a composição é a melhor abordagem a ser usada? Use a composição em quandoo relacionamento não for estritamente hierárquico e/ou quando você quiserreduzir o acoplamento rígido entre as classes.
Ou também poderíamos dizer que, enquanto a herança modela as relações, a composição se concentra na funcionalidade. Para ajudar, considere o seguinte:
-
Use a herança quando os objetos forem naturalmente hierárquicos. Por exemplo,
Animal > Bird > Parrot
. -
Use a composição quando os objetos compartilharem funcionalidades, mas não estiverem relacionados. Por exemplo, um
Printer
e umScanner
usam umDeviceManager
.
Evite cadeias de herança profundas
Cadeias de herança profundas (muitos níveis de relacionamentos pai-filho) podem dificultar a leitura e a manutenção do seu código. Isso é um problema porquealterações em uma classe principal podem afetar involuntariamente muitas classes secundárias. Além disso, a d ebuggingse torna complexa à medida que o comportamento se espalha por vários níveis.
A prática recomendada nesse caso é kmanter as hierarquias rasas. Além disso,considere usar a composição (como mencionei anteriormente) ou dividir uma cadeia em hierarquias separadas se você achar que ela está se tornando muito profunda.
Conclusão
A herança é um dos principais pilares da programação orientada a objetos que permite que desenvolvedores como você criem códigos reutilizáveis, modulares e dimensionáveis. Se você conseguir dominar a herança, será fácil simplificar sistemas complexos.
Uma boa maneira de aprofundar seu conhecimento é tentar criar estruturas de herança em seus projetos. Comece de forma simples e depois experimente hierarquias mais complexas para ver como elas funcionam na prática.
Se quiser se aprofundar ainda mais, você pode conferir nosso curso Conceitos do Paradigma de Programação para entender melhor a herança e outras ideias. Nosso plano de carreira de Desenvolvedor Python também é um bom recurso que oferece um caminho abrangente para o desenvolvimento de habilidades avançadas de programação que equiparão você para o desenvolvimento de software.
Profissional experiente em dados e escritor que tem paixão por capacitar aspirantes a especialistas no espaço de dados.
Perguntas frequentes sobre herança em Python
O que é herança em Python?
A herança em Python é um mecanismo que permite que uma classe herde atributos e métodos de outra classe, promovendo a reutilização do código e estruturas de classes hierárquicas.
Como funciona a herança Python?
A herança Python funciona definindo uma nova classe que herda atributos e métodos de uma classe existente, permitindo estruturas de classes hierárquicas.
Quais são os tipos de herança em Python?
O Python oferece suporte a vários tipos de herança, incluindo herança única, múltipla, multinível e hierárquica.
Qual é a diferença entre herança única e múltipla em Python?
A herança única envolve uma classe principal, enquanto a herança múltipla permite que uma classe herde de várias classes principais.
Como você implementa a herança em Python?
Implemente a herança definindo uma nova classe que especifique uma classe pai em sua definição, usando a sintaxe class ChildClass(ParentClass):
.
Quais são os benefícios de usar a herança em Python?
A herança promove a reutilização do código, permite a criação de estruturas de classes hierárquicas e oferece suporte ao polimorfismo, o que torna o código mais flexível e passível de manutenção.
Como você implementa a herança múltipla em Python?
A herança múltipla é implementada pela definição de uma classe que herda de mais de uma classe base, com a resolução de métodos de manipulação do Python usando a ordem de resolução de métodos (MRO).

blog
6 práticas recomendadas de Python para um código melhor

blog
5 desafios Python para desenvolver suas habilidades

DataCamp Team
5 min
blog
Mais de 60 projetos Python para todos os níveis de conhecimento

Bekhruz Tuychiev
16 min
tutorial
Programação orientada a objetos em Python (OOP): Tutorial
tutorial
Tutorial de Python

DataCamp Team
3 min
tutorial