1
resposta

Hora da prática: métodos especiais e atributos

classes\veiculo.py

from abc import ABC, abstractmethod

class Veiculo(ABC):
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo
        self._ligado = False

    def __str__(self):
        return f"Marca: {self.marca} | Modelo: {self.modelo} | Estado: {"Ligado" if self._ligado else "Desligado"}"
    
    @abstractmethod
    def ligar(self):
        self._ligado = True
        print(f"{self.modelo} Ligado.")

    
    

classes\carro.py

from classes.veiculo import Veiculo

class Carro(Veiculo):
    def __init__(self, marca, modelo, cor):
        super().__init__(marca, modelo)
        self.cor = cor

    def __str__(self):
        return f"{super().__str__()} | Cor: {self.cor}"
    
    def ligar(self):
        print(f"Ligando carro {self.modelo}...")
        return super().ligar()

main.py

from classes.carro import Carro

carro1 = Carro("Fiat", "Uno", "Cinza")
carro1.ligar()
carro2 = Carro("Volkswagen", "Gol", "Preto")
carro3 = Carro("Chevrolet", "Onix", "Branco")
carro3.ligar()

print(carro1)
print(carro2)
print(carro3)
1 resposta

Olá, Luiz Fernando. Como vai?

Parabéns pela resolução da atividade! O seu código está excelente e demonstra uma compreensão muito sólida sobre os pilares da Orientação a Objetos em Python, especialmente no uso de Herança, Polimorfismo e o reaproveitamento de código com o método super().

A forma como você utilizou o método especial __str__ nas duas classes ficou impecável. No arquivo carro.py, ao chamar super().__str__(), você evitou a repetição desnecessária de código e garantiu que o carro aproveitasse a formatação base da marca e do modelo criada na classe mãe, adicionando apenas o atributo específico (cor). Essa é a legítima elegância do paradigma focado em objetos.

Como um bom par de programação, quero analisar com você dois pontos importantes do seu código: um pequeno erro de sintaxe que pode quebrar a execução dependendo da versão do Python, e um detalhe conceitual sobre Classes Abstratas que é muito cobrado no mercado.


1. O Ajuste de Sintaxe (Recuo do Decorador)

No seu arquivo veiculo.py, o decorador @abstractmethod e a assinatura do método ligar acabaram ficando com um recuo (indentação) desalinhado. No Python, a indentação define o escopo.

Se o decorador estiver empurrado para a direita, o interpretador vai gerar um erro de indentação (IndentationError). O alinhamento correto dentro da classe deve ser este:

    def __str__(self):
        return f"Marca: {self.marca} | Modelo: {self.modelo} | Estado: {'Ligado' if self._ligado else 'Desligado'}"
        
    @abstractmethod
    def ligar(self):
        self._ligado = True
        print(f"{self.modelo} Ligado.")

Dica extra: Repare que dentro da f-string do __str__, eu alterei as aspas duplas do {"Ligado" if ...} para aspas simples {'Ligado' if ...}. Usar o mesmo tipo de aspas dentro e fora da f-string costuma confundir o Python em algumas versões.


2. O Conceito de Método Abstrato com Implementação

O seu método ligar em Veiculo foi marcado com @abstractmethod, mas você colocou um corpo de código dentro dele (mudando o estado para True e dando um print). E no seu Carro, você chamou esse comportamento usando super().ligar().

O código funciona? Sim, o Python permite de forma legítima que um método abstrato tenha uma implementação base que pode ser herdada pelas classes filhas através do super().

Onde está o ponto de atenção conceitual? No design de software, quando definimos um método como abstrato, a nossa intenção principal é criar um contrato puro. Estamos dizendo: "Eu não sei COMO um veículo liga (afinal, um carro liga com a chave, uma moto no pedal e um barco puxando uma corda), mas eu obrigo qualquer classe filha a criar a sua própria lógica de ligar".

Se a classe mãe já sabe exatamente o que o método faz (mudar a variável _ligado para True), o mais recomendado conceitualmente seria manter o método ligar como um método concreto comum na classe mãe.

Veja a diferença das duas abordagens de mercado:

  • Abordagem A (Método Concreto Comum): Se todos os veículos ligam do mesmo jeito (apenas alterando o atributo _ligado), remova o @abstractmethod. Assim, a classe Carro nem precisará reescrever o método ligar, ela já herda o comportamento automaticamente. O seu arquivo main.py funcionará sem você precisar digitar a função ligar em cada classe filha.
  • Abordagem B (Contrato Abstrato Puro): Se cada veículo liga de um jeito único, o método na classe mãe deve apenas ditar a regra, usando pass:
# Em veiculo.py
@abstractmethod
def ligar(self):
    pass

# Em carro.py
def ligar(self):
    print(f"Ligando o carro {self.modelo} através da chave...")
    self._ligado = True # O carro resolve como altera o próprio estado

A sua solução prática está muito bem estruturada, e ver você aplicando herança com super() logo no início dos estudos mostra que você tem muita facilidade com a lógica de POO.

Corrigindo o detalhe do alinhamento do decorador, o seu projeto está pronto para rodar perfeitamente e consumir qualquer API! Continue com essa constância fantástica.

Espero que possa ter lhe ajudado!