Python & AI Tutorials Logo
Programação Python

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.

python
# 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: Buddy

Mesmo 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.

Animal

+name

+init(name)

+speak()

Dog

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:

python
# 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:

python
# 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:

python
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:

python
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.00

A 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:

python
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)

LivingThing

+name

+is_alive()

Animal

+species

+move()

Dog

+breed

+bark()

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.

python
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:

python
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.26544

O 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:

python
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: S12345

Note 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.

python
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:

python
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: Engineering

Ao 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:

python
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: 1

O 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:

python
class Vehicle:
    def __init__(self, brand):
        self.brand = brand
 
class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)  # Preferível
        self.model = model

Use 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:

python
class Car(Vehicle):
    def __init__(self, brand, model):
        Vehicle.__init__(self, brand)  # Explícito, mas menos flexível
        self.model = model

Para 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__:

python
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 ERROR

32.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.

python
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:

python
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.

python
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 data

Nenhuma 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:

python
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: DLROWOLLEH

O 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:

python
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: False

Note 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:

python
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: False

é um

é um

é um

instância tesla

ElectricCar

Car

Vehicle

O 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:

python
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: False

Isso é 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:

python
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: True

Note 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:

python
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: $8000

No entanto, em muitos casos, polimorfismo (cada classe implementar um método comum) é melhor do que verificação de tipos:

python
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: $8000

Essa 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 é um Animal)
  • 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.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai