32. Estendendo Classes com Herança e Polimorfismo
No Capítulo 30, aprendemos como criar nossas próprias classes para modelar conceitos do mundo real. Criamos classes como BankAccount e Student que agrupavam dados e comportamento. Mas o que acontece quando você precisa criar uma nova classe que é parecida com uma existente, mas com algumas diferenças ou adições?
Herança (inheritance) é o mecanismo do Python para criar novas classes com base em classes existentes. Em vez de copiar e colar código, você pode criar uma subclasse (subclass) que automaticamente recebe todos os atributos e métodos de uma classe pai (parent class) (também chamada de classe base (base class) ou superclasse (superclass)) e, então, adicionar ou modificar o que você precisar.
Este capítulo explora como a herança permite construir hierarquias de classes relacionadas, como personalizar comportamento herdado e como o polimorfismo (polymorphism) permite que classes diferentes sejam usadas de forma intercambiável quando elas compartilham interfaces comuns.
32.1) Criando Subclasses a partir de Classes Existentes
32.1.1) A Sintaxe Básica da Herança
A herança permite que você crie uma nova classe (chamada de subclasse (subclass) ou classe filha (child class)) com base em uma classe existente (chamada de classe pai (parent class) ou classe base (base class)). A subclasse herda automaticamente todos os métodos e atributos da classe pai.
Quando você cria uma subclasse, você especifica a classe pai entre parênteses depois do nome da classe.
# Classe pai
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
# Subclasse herdando de Animal
class Dog(Animal):
pass # Nenhum código adicional é necessário por enquanto
# Crie uma instância de Dog
buddy = Dog("Buddy")
print(buddy.speak()) # Output: Buddy makes a sound
print(buddy.name) # Output: BuddyMesmo que Dog não tenha código próprio (apenas pass), ela herda tudo de Animal. A classe Dog automaticamente tem o método __init__ e o método speak do seu pai.
32.1.2) Por que a Herança Importa
A herança resolve um problema comum em programação: duplicação de código. Imagine que você está construindo um sistema para gerenciar diferentes tipos de funcionários:
# Sem herança - muita duplicação
class FullTimeEmployee:
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.salary = salary
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class PartTimeEmployee:
def __init__(self, name, employee_id, hourly_rate):
self.name = name
self.employee_id = employee_id
self.hourly_rate = hourly_rate
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"Note como name, employee_id e get_info() estão duplicados. Com herança, podemos eliminar essa duplicação:
# Com herança - código compartilhado na classe pai
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, salary):
Employee.__init__(self, name, employee_id) # Chama o __init__ do pai
# Observação: vamos aprender um jeito melhor de fazer isso com super() na Seção 32.3
self.salary = salary
class PartTimeEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate):
Employee.__init__(self, name, employee_id) # Chama o __init__ do pai
self.hourly_rate = hourly_rate
# Ambas as subclasses herdam get_info()
alice = FullTimeEmployee("Alice", "E001", 75000)
bob = PartTimeEmployee("Bob", "E002", 25)
print(alice.get_info()) # Output: Alice (ID: E001)
print(bob.get_info()) # Output: Bob (ID: E002)Agora os atributos e métodos comuns ficam em Employee, e cada subclasse só define o que a torna única.
32.1.3) Adicionando Novos Métodos a Subclasses
Subclasses podem adicionar seus próprios métodos que o pai não tem:
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def get_description(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, num_doors):
Vehicle.__init__(self, brand, model)
self.num_doors = num_doors
def honk(self): # Novo método específico de Car
return "Beep beep!"
class Motorcycle(Vehicle):
def __init__(self, brand, model, has_sidecar):
Vehicle.__init__(self, brand, model)
self.has_sidecar = has_sidecar
def rev_engine(self): # Novo método específico de Motorcycle
return "Vroom vroom!"
# Cada subclasse tem os métodos do pai mais os seus próprios
my_car = Car("Toyota", "Camry", 4)
print(my_car.get_description()) # Output: Toyota Camry
print(my_car.honk()) # Output: Beep beep!
my_bike = Motorcycle("Harley", "Sportster", False)
print(my_bike.get_description()) # Output: Harley Sportster
print(my_bike.rev_engine()) # Output: Vroom vroom!A classe Car tem tanto get_description() (herdado) quanto honk() (dela). A classe Motorcycle tem get_description() (herdado) e rev_engine() (dela).
32.1.4) Adicionando Novos Atributos a Subclasses
Subclasses também podem adicionar seus próprios atributos de instância. Normalmente, você faz isso no método __init__ da subclasse:
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
class SavingsAccount(BankAccount):
def __init__(self, account_number, balance, interest_rate):
BankAccount.__init__(self, account_number, balance)
self.interest_rate = interest_rate # Novo atributo
def apply_interest(self): # Novo método usando o novo atributo
interest = self.balance * self.interest_rate
self.balance += interest
return interest
# SavingsAccount tem todos os atributos de BankAccount mais interest_rate
savings = SavingsAccount("SA001", 1000, 0.03)
savings.deposit(500) # Método herdado
print(f"Balance: ${savings.balance}") # Output: Balance: $1500
interest_earned = savings.apply_interest()
print(f"Interest earned: ${interest_earned:.2f}") # Output: Interest earned: $45.00
print(f"New balance: ${savings.balance:.2f}") # Output: New balance: $1545.00A SavingsAccount tem account_number e balance de BankAccount, além do seu próprio atributo interest_rate.
32.1.5) Vários Níveis de Herança
Classes podem herdar de classes que, elas mesmas, herdam de outras classes, criando uma hierarquia de herança:
class LivingThing:
def __init__(self, name):
self.name = name
def is_alive(self):
return True
class Animal(LivingThing):
def __init__(self, name, species):
LivingThing.__init__(self, name)
self.species = species
def move(self):
return f"{self.name} is moving"
class Dog(Animal):
def __init__(self, name, breed):
Animal.__init__(self, name, "Dog")
self.breed = breed
def bark(self):
return f"{self.name} says: Woof!"
# Dog herda de Animal, que herda de LivingThing
max_dog = Dog("Max", "Golden Retriever")
# Métodos de todos os três níveis funcionam
print(max_dog.is_alive()) # Output: True (from LivingThing)
print(max_dog.move()) # Output: Max is moving (from Animal)
print(max_dog.bark()) # Output: Max says: Woof! (from Dog)
# Atributos de todos os três níveis existem
print(max_dog.name) # Output: Max (from LivingThing)
print(max_dog.species) # Output: Dog (from Animal)
print(max_dog.breed) # Output: Golden Retriever (from Dog)Dog herda de Animal, que herda de LivingThing. Isso significa que Dog tem acesso a métodos e atributos de ambas as classes pai.
32.2) Sobrescrevendo Métodos em Subclasses
32.2.1) O que Significa Sobrescrita de Método
Às vezes uma subclasse precisa mudar como um método herdado funciona. Sobrescrita de método (method overriding) significa definir um método na subclasse com o mesmo nome de um método na classe pai. Quando você chama esse método em uma instância da subclasse, o Python usa a versão da subclasse em vez da do pai.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self): # Sobrescreve o método speak do pai
return f"{self.name} says: Woof!"
class Cat(Animal):
def speak(self): # Sobrescreve com um comportamento diferente
return f"{self.name} says: Meow!"
# Cada classe tem sua própria versão de speak()
generic_animal = Animal("Generic")
print(generic_animal.speak()) # Output: Generic makes a sound
buddy = Dog("Buddy")
print(buddy.speak()) # Output: Buddy says: Woof!
whiskers = Cat("Whiskers")
print(whiskers.speak()) # Output: Whiskers says: Meow!Quando você chama buddy.speak(), o Python procura o método speak na classe Dog primeiro, já que buddy é uma instância de Dog. Como Dog define seu próprio método speak, o Python usa essa versão. Se Dog não tivesse um método speak, o Python então procuraria na classe pai Animal e usaria essa versão.
Essa ordem de busca — começando pela classe da instância e depois indo para a classe pai — é como a sobrescrita de métodos funciona e como subclasses personalizam comportamento herdado.
32.2.2) Por que Sobrescrever Métodos?
A sobrescrita de métodos permite criar versões especializadas de um comportamento geral. Considere uma hierarquia de formas:
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0 # Implementação padrão
def describe(self):
return f"{self.name} with area {self.area()}"
class Rectangle(Shape):
def __init__(self, width, height):
Shape.__init__(self, "Rectangle")
self.width = width
self.height = height
def area(self): # Sobrescreve com um cálculo específico de retângulo
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
Shape.__init__(self, "Circle")
self.radius = radius
def area(self): # Sobrescreve com um cálculo específico de círculo
return 3.14159 * self.radius ** 2
# Cada forma calcula a área de um jeito diferente
rect = Rectangle(5, 3)
print(rect.describe()) # Output: Rectangle with area 15
circle = Circle(4)
print(circle.describe()) # Output: Circle with area 50.26544O método describe() é herdado por ambas as subclasses e funciona corretamente porque cada subclasse fornece sua própria implementação de area().
Quando você chama rect.describe(), o método herdado describe() é executado, mas self se refere à instância de Rectangle. Então, quando describe() chama self.area(), o Python procura area() na classe Rectangle primeiro e encontra a versão sobrescrita.
32.2.3) Sobrescrevendo __init__ e Chamando a Inicialização do Pai
Quando você sobrescreve __init__, normalmente você precisa chamar o __init__ do pai para garantir que a inicialização do pai aconteça:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I'm {self.name}, {self.age} years old"
class Student(Person):
def __init__(self, name, age, student_id, major):
# Chama o __init__ do pai para definir name e age
Person.__init__(self, name, age)
# Depois define atributos específicos do estudante
self.student_id = student_id
self.major = major
alice = Student("Alice", 20, "S12345", "Computer Science")
print(alice.name) # Output: Alice
print(alice.student_id) # Output: S12345Note como Student.__init__ primeiro chama Person.__init__(self, name, age) para inicializar os atributos da classe pai, e depois adiciona seus próprios atributos.
32.3) Usando super() para Acessar Comportamento do Pai
32.3.1) O que super() Faz
Na seção anterior, chamamos métodos do pai explicitamente: ParentClass.method(self, ...). O Python oferece um jeito mais limpo: a função super(). super() retorna um objeto temporário que permite chamar métodos da classe pai sem nomear o pai explicitamente.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Mais limpo do que Animal.__init__(self, name)
self.breed = breed
def speak(self):
parent_sound = super().speak() # Chama speak() do pai
return f"{parent_sound} - specifically, Woof!"
buddy = Dog("Buddy", "Labrador")
print(buddy.speak())
# Output: Buddy makes a sound - specifically, Woof!Usar super() tem várias vantagens:
- Você não precisa nomear a classe pai explicitamente
- Funciona corretamente com herança múltipla (coberta mais adiante)
- Torna o código mais fácil de manter se você mudar a classe pai
32.3.2) Usando super() no __init__
O uso mais comum de super() é chamar o __init__ do pai:
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
self.is_active = True
def deactivate(self):
self.is_active = False
class Manager(Employee):
def __init__(self, name, employee_id, department):
super().__init__(name, employee_id) # Inicializa atributos do pai
self.department = department
self.team = [] # Atributo específico de Manager
def add_team_member(self, employee):
self.team.append(employee)
# Manager recebe todos os atributos de Employee mais os seus próprios
sarah = Manager("Sarah", "M001", "Engineering")
print(sarah.name) # Output: Sarah
print(sarah.is_active) # Output: True
print(sarah.department) # Output: EngineeringAo chamar super().__init__(name, employee_id), a classe Manager garante que toda a lógica de inicialização de Employee rode, incluindo definir is_active como True.
32.3.3) Estendendo Métodos do Pai com super()
Você pode usar super() para estender um método do pai em vez de substituí-lo completamente:
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
self.transaction_count = 0
def deposit(self, amount):
self.balance += amount
self.transaction_count += 1
return self.balance
class CheckingAccount(BankAccount):
def __init__(self, account_number, balance, overdraft_limit):
super().__init__(account_number, balance)
self.overdraft_limit = overdraft_limit
def deposit(self, amount):
was_overdrawn = self.balance < 0
# Chama deposit do pai para lidar com a lógica básica
new_balance = super().deposit(amount)
# Adiciona comportamento específico de conta corrente
if was_overdrawn and new_balance >= 0:
print("Account is no longer overdrawn")
return new_balance
checking = CheckingAccount("C001", -50, 100)
checking.deposit(75)
# Output: Account is no longer overdrawn
print(f"Balance: ${checking.balance}") # Output: Balance: $25
print(f"Transactions: {checking.transaction_count}") # Output: Transactions: 1O método CheckingAccount.deposit() chama super().deposit(amount) para lidar com a lógica básica do depósito (atualizar saldo e contagem de transações) e depois adiciona sua própria verificação do status de cheque especial.
32.3.4) Quando Usar super() vs Chamada Direta ao Pai
Use super() na maioria dos casos:
class Vehicle:
def __init__(self, brand):
self.brand = brand
class Car(Vehicle):
def __init__(self, brand, model):
super().__init__(brand) # Preferível
self.model = modelUse chamadas diretas ao pai quando você precisar chamar um pai específico em um cenário de herança múltipla (coberto mais adiante) ou quando você quiser ser explícito sobre qual pai está chamando:
class Car(Vehicle):
def __init__(self, brand, model):
Vehicle.__init__(self, brand) # Explícito, mas menos flexível
self.model = modelPara herança simples (um pai), super() quase sempre é a melhor escolha.
32.3.5) super() com Outros Métodos
Você pode usar super() com qualquer método, não apenas com __init__:
class TextProcessor:
def process(self, text):
# Processamento básico: remover espaços em branco nas extremidades
return text.strip()
class UppercaseProcessor(TextProcessor):
def process(self, text):
# Primeiro faz o processamento do pai
processed = super().process(text)
# Depois adiciona conversão para maiúsculas
return processed.upper()
class PrefixProcessor(UppercaseProcessor):
def __init__(self, prefix):
self.prefix = prefix
def process(self, text):
# Primeiro faz o processamento do pai (que chama o pai dele também)
processed = super().process(text)
# Depois adiciona o prefixo
return f"{self.prefix}: {processed}"
processor = PrefixProcessor("ALERT")
result = processor.process(" system error ")
print(result) # Output: ALERT: SYSTEM ERROR32.4) Polimorfismo: Trabalhando com Classes Compatíveis
32.4.1) O que Polimorfismo Significa
Polimorfismo (do grego: "muitas formas") é a capacidade de tratar objetos de classes diferentes da mesma forma se eles fornecerem os mesmos métodos.
Em Python, se várias classes têm métodos com o mesmo nome, você pode chamar esses métodos sem saber a classe exata do objeto.
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Woof!"
class Cat:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Meow!"
class Bird:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Tweet!"
# Função que funciona com qualquer objeto que tenha um método speak()
def make_animal_speak(animal):
print(animal.speak())
# Funciona com classes diferentes
buddy = Dog("Buddy")
whiskers = Cat("Whiskers")
tweety = Bird("Tweety")
make_animal_speak(buddy) # Output: Buddy says: Woof!
make_animal_speak(whiskers) # Output: Whiskers says: Meow!
make_animal_speak(tweety) # Output: Tweety says: Tweet!A função make_animal_speak() não se importa com qual é a classe do parâmetro animal — ela só precisa que o objeto tenha um método speak(). Isso é polimorfismo em ação.
32.4.2) Polimorfismo com Herança
O polimorfismo é especialmente poderoso com herança, onde subclasses sobrescrevem métodos do pai:
class PaymentMethod:
def process_payment(self, amount):
return f"Processing ${amount:.2f}"
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.card_number = card_number
def process_payment(self, amount):
return f"Charging ${amount:.2f} to credit card ending in {self.card_number[-4:]}"
class PayPal(PaymentMethod):
def __init__(self, email):
self.email = email
def process_payment(self, amount):
return f"Sending ${amount:.2f} via PayPal to {self.email}"
class BankTransfer(PaymentMethod):
def __init__(self, account_number):
self.account_number = account_number
def process_payment(self, amount):
return f"Transferring ${amount:.2f} to account {self.account_number}"
# Função que funciona com qualquer PaymentMethod
def complete_purchase(payment_method, amount):
print(payment_method.process_payment(amount))
print("Purchase complete!")
# Todos os métodos de pagamento funcionam com a mesma função
credit = CreditCard("1234567890123456")
paypal = PayPal("user@example.com")
bank = BankTransfer("9876543210")
complete_purchase(credit, 99.99)
# Output: Charging $99.99 to credit card ending in 3456
# Output: Purchase complete!
complete_purchase(paypal, 49.50)
# Output: Sending $49.50 via PayPal to user@example.com
# Output: Purchase complete!
complete_purchase(bank, 199.00)
# Output: Transferring $199.00 to account 9876543210
# Output: Purchase complete!A função complete_purchase() funciona com qualquer subclasse de PaymentMethod. Cada subclasse fornece sua própria implementação de process_payment(), mas a função não precisa saber com qual classe específica ela está trabalhando.
32.4.3) Duck Typing: "Se Anda como um Pato..."
O polimorfismo do Python não exige que as classes sejam relacionadas por herança. Isso é chamado de duck typing (duck typing): "Se anda como um pato e grasna como um pato, então é um pato." Em outras palavras, o Python se importa com o que um objeto consegue fazer (seus métodos), não com o que ele é (sua classe). Se um objeto tem os métodos de que você precisa, você pode usá-lo, independentemente da hierarquia de classes dele.
class FileWriter:
def __init__(self, filename):
self.filename = filename
def write(self, data):
print(f"Writing to {self.filename}: {data}")
class DatabaseWriter:
def __init__(self, table_name):
self.table_name = table_name
def write(self, data):
print(f"Inserting into {self.table_name}: {data}")
class ConsoleWriter:
def write(self, data):
print(f"Console output: {data}")
# Função que funciona com qualquer objeto que tenha um método write()
def save_data(writer, data):
writer.write(data)
# As três classes funcionam, mesmo que não sejam relacionadas
file_writer = FileWriter("data.txt")
db_writer = DatabaseWriter("users")
console_writer = ConsoleWriter()
save_data(file_writer, "User data")
# Output: Writing to data.txt: User data
save_data(db_writer, "User data")
# Output: Inserting into users: User data
save_data(console_writer, "User data")
# Output: Console output: User dataNenhuma dessas classes herda de um pai em comum, mas todas funcionam com save_data() porque todas têm um método write(). Isso é duck typing — a função não se importa com a classe, apenas com a interface (os métodos disponíveis).
32.4.4) Exemplo Prático: Um Sistema de Plugins
Polimorfismo permite sistemas flexíveis e extensíveis. Aqui vai um sistema simples de plugins para processamento de dados:
class DataProcessor:
def process(self, data):
return data # Implementação base não faz nada
class UppercaseProcessor(DataProcessor):
def process(self, data):
return data.upper()
class ReverseProcessor(DataProcessor):
def process(self, data):
return data[::-1]
class RemoveSpacesProcessor(DataProcessor):
def process(self, data):
return data.replace(" ", "")
class DataPipeline:
def __init__(self):
self.processors = []
def add_processor(self, processor):
self.processors.append(processor)
def run(self, data):
result = data
for processor in self.processors:
result = processor.process(result) # Chamada polimórfica
return result
# Monte um pipeline de processamento
pipeline = DataPipeline()
pipeline.add_processor(UppercaseProcessor())
pipeline.add_processor(RemoveSpacesProcessor())
pipeline.add_processor(ReverseProcessor())
# Processe dados através do pipeline
input_data = "Hello World"
output = pipeline.run(input_data)
print(f"Input: {input_data}") # Output: Input: Hello World
print(f"Output: {output}") # Output: Output: DLROWOLLEHO DataPipeline não precisa saber quais processadores específicos ele contém — ele só chama process() em cada um. Você consegue adicionar novos tipos de processador facilmente sem mudar o código do pipeline.
32.5) Verificando Tipos e Relações entre Classes (isinstance, issubclass)
32.5.1) Verificando Tipos de Instância com isinstance()
Às vezes você precisa verificar se um objeto é uma instância de uma classe específica. A função isinstance() faz isso:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
buddy = Dog()
whiskers = Cat()
# Verifique se um objeto é uma instância de uma classe
print(isinstance(buddy, Dog)) # Output: True
print(isinstance(buddy, Animal)) # Output: True (Dog inherits from Animal)
print(isinstance(buddy, Cat)) # Output: False
print(isinstance(whiskers, Cat)) # Output: True
print(isinstance(whiskers, Animal)) # Output: True
print(isinstance(whiskers, Dog)) # Output: FalseNote que isinstance(buddy, Animal) retorna True mesmo que buddy seja uma instância de Dog. Isso acontece porque Dog herda de Animal, então uma instância de Dog também é considerada uma instância de Animal.
32.5.2) Por que isinstance() Respeita a Herança
A função isinstance() verifica toda a cadeia de herança:
class Vehicle:
pass
class Car(Vehicle):
pass
class ElectricCar(Car):
pass
tesla = ElectricCar()
# Verifique todos os níveis de herança
print(isinstance(tesla, ElectricCar)) # Output: True
print(isinstance(tesla, Car)) # Output: True
print(isinstance(tesla, Vehicle)) # Output: True
print(isinstance(tesla, str)) # Output: FalseO objeto tesla é uma instância de ElectricCar, mas também é uma instância de Car e Vehicle por causa da herança.
32.5.3) Verificando Vários Tipos ao Mesmo Tempo
Você pode verificar se um objeto é uma instância de qualquer uma de várias classes passando uma tupla:
class Dog:
pass
class Cat:
pass
class Bird:
pass
def is_pet(animal):
return isinstance(animal, (Dog, Cat, Bird))
buddy = Dog()
whiskers = Cat()
tweety = Bird()
rock = "just a rock"
print(is_pet(buddy)) # Output: True
print(is_pet(whiskers)) # Output: True
print(is_pet(tweety)) # Output: True
print(is_pet(rock)) # Output: FalseIsso é mais conciso do que escrever isinstance(animal, Dog) or isinstance(animal, Cat) or isinstance(animal, Bird).
32.5.4) Verificando Relações entre Classes com issubclass()
A função issubclass() verifica se uma classe é subclasse de outra:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Poodle(Dog):
pass
# Verifique relações entre classes
print(issubclass(Dog, Animal)) # Output: True
print(issubclass(Cat, Animal)) # Output: True
print(issubclass(Poodle, Dog)) # Output: True
print(issubclass(Poodle, Animal)) # Output: True (indirect inheritance)
print(issubclass(Dog, Cat)) # Output: False
# Uma classe é considerada subclasse dela mesma
print(issubclass(Dog, Dog)) # Output: TrueNote que issubclass() funciona com classes, não com instâncias. Use isinstance() para instâncias e issubclass() para classes.
32.5.5) Casos de Uso Práticos para Verificação de Tipos
A verificação de tipos é útil quando você precisa de comportamento diferente para tipos diferentes:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
class Contractor:
def __init__(self, name, hourly_rate, hours):
self.name = name
self.hourly_rate = hourly_rate
self.hours = hours
def calculate_payment(worker):
# Ao usar isinstance() com herança, verifique subclasses antes de classes pai
if isinstance(worker, Manager): # Verifique Manager primeiro
return worker.salary + worker.bonus
elif isinstance(worker, Employee): # Depois verifique Employee (classe pai)
return worker.salary
elif isinstance(worker, Contractor):
return worker.hourly_rate * worker.hours
else:
return 0
alice = Employee("Alice", 50000)
bob = Manager("Bob", 70000, 10000)
charlie = Contractor("Charlie", 50, 160)
print(f"Alice's payment: ${calculate_payment(alice)}") # Output: Alice's payment: $50000
print(f"Bob's payment: ${calculate_payment(bob)}") # Output: Bob's payment: $80000
print(f"Charlie's payment: ${calculate_payment(charlie)}")# Output: Charlie's payment: $8000No entanto, em muitos casos, polimorfismo (cada classe implementar um método comum) é melhor do que verificação de tipos:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def calculate_payment(self):
return self.salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def calculate_payment(self):
return self.salary + self.bonus
class Contractor:
def __init__(self, name, hourly_rate, hours):
self.name = name
self.hourly_rate = hourly_rate
self.hours = hours
def calculate_payment(self):
return self.hourly_rate * self.hours
# Nenhuma verificação de tipo é necessária - o polimorfismo resolve
workers = [
Employee("Alice", 50000),
Manager("Bob", 70000, 10000),
Contractor("Charlie", 50, 160)
]
for worker in workers:
payment = worker.calculate_payment() # Chamada polimórfica
print(f"{worker.name}'s payment: ${payment}")Output:
Alice's payment: $50000
Bob's payment: $80000
Charlie's payment: $8000Essa abordagem polimórfica é mais flexível e mais fácil de estender com novos tipos de trabalhador. Você não precisa modificar o código que faz a chamada quando adiciona uma nova classe de trabalhador — apenas garanta que ela tenha um método calculate_payment().
Herança e polimorfismo são ferramentas poderosas para organizar código e criar sistemas flexíveis e extensíveis. Ao criar subclasses, você pode reutilizar código existente enquanto adiciona ou modifica comportamento. Ao sobrescrever métodos, você pode personalizar como as subclasses funcionam. E, ao usar polimorfismo, você pode escrever código que funciona com muitas classes diferentes por meio de uma interface comum.
A chave é usar esses recursos com cuidado:
- Use herança quando existe um relacionamento real de "é um" (um
Dogé umAnimal) - Sobrescreva métodos para especializar comportamento, não para mudar completamente o que uma classe faz
- Use
super()para estender o comportamento do pai em vez de substituí-lo totalmente - Prefira polimorfismo (métodos comuns) em vez de verificação de tipos quando possível
À medida que você for construindo programas maiores, essas técnicas orientadas a objetos vão ajudar você a criar um código mais fácil de entender, manter e estender.