Bem-vindo à aula sobre Classes e Objetos em Python! Até agora, você provavelmente já escreveu scripts que usam funções e estruturas de dados, mas e se precisar representar algo mais complexo, como um usuário de um sistema, um carro ou uma conta bancária? A programação orientada a objetos (POO) oferece uma maneira poderosa de organizar código, agrupando dados e comportamentos relacionados em entidades chamadas classes e objetos. Nesta aula, vamos explorar os conceitos essenciais de POO em Python, desde a definição de uma classe até a criação e uso de objetos.

Python é uma linguagem que suporta múltiplos paradigmas, e a POO é um dos mais importantes, especialmente para projetos grandes e colaborativos. Ao dominar classes e objetos, você poderá escrever código mais modular, reutilizável e fácil de manter. Vamos começar!

class

Uma classe é um modelo (ou blueprint) para criar objetos. Ela define quais atributos (dados) e métodos (funções) os objetos criados a partir dela terão. Pense em uma classe como uma forma de bolo: a forma define o formato, mas cada bolo feito com ela pode ter coberturas diferentes. Em Python, definimos uma classe usando a palavra-chave class, seguida do nome da classe (por convenção, em CamelCase).

Sintaxe básica:

class NomeDaClasse:
    # corpo da classe
    pass

O corpo da classe pode conter atributos (variáveis de classe) e métodos (funções). Vejamos um exemplo simples:

class Pessoa:
    # atributo de classe (compartilhado por todas as instâncias)
    especie = "Humano"
    
    # método
    def saudacao(self):
        return "Olá!"

Neste exemplo, definimos a classe Pessoa com um atributo de classe especie e um método saudacao. Note que o método recebe o parâmetro self (mais sobre isso em breve). Por enquanto, não criamos nenhum objeto ainda; apenas definimos a estrutura.

__init__ e self

O método __init__ é um método especial chamado construtor. Ele é executado automaticamente quando criamos um novo objeto (instância) da classe. Serve para inicializar os atributos do objeto com valores específicos. O nome __init__ é fixo e sempre deve ter self como primeiro parâmetro.

O parâmetro self refere-se à própria instância atual. Dentro dos métodos, usamos self para acessar os atributos e outros métodos do objeto. Quando chamamos um método de um objeto, o Python passa automaticamente o objeto como o primeiro argumento, que é atribuído a self.

Exemplo de uso do construtor:

class Pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
    
    def apresentar(self):
        return f"Meu nome é {self.nome} e tenho {self.idade} anos."

Ao criar um objeto, passamos os argumentos que o __init__ espera (exceto self):

p1 = Pessoa("Ana", 25)
print(p1.nome)  # Ana
print(p1.apresentar())  # Meu nome é Ana e tenho 25 anos.

Observe que não chamamos __init__ explicitamente; ele é invocado automaticamente quando usamos Pessoa("Ana", 25).

Atributos e métodos

Atributos são variáveis que pertencem a um objeto (atributos de instância) ou à classe (atributos de classe). Já os métodos são funções definidas dentro da classe que descrevem os comportamentos dos objetos. Vamos detalhar:

  • Atributos de instância: definidos dentro de métodos, geralmente no __init__, precedidos por self.. Cada objeto tem seus próprios valores.
  • Atributos de classe: definidos diretamente no corpo da classe, fora de métodos. São compartilhados por todas as instâncias.
  • Métodos de instância: funções que recebem self como primeiro parâmetro e podem acessar/modificar atributos do objeto.

Exemplo completo:

class ContaBancaria:
    # atributo de classe
    banco = "Banco do Brasil"
    
    def __init__(self, titular, saldo=0):
        # atributos de instância
        self.titular = titular
        self.saldo = saldo
    
    def depositar(self, valor):
        if valor > 0:
            self.saldo += valor
            return f"Depósito de R${valor} realizado. Novo saldo: R${self.saldo}"
        return "Valor inválido"
    
    def sacar(self, valor):
        if 0 < valor <= self.saldo:
            self.saldo -= valor
            return f"Saque de R${valor} realizado. Novo saldo: R${self.saldo}"
        return "Saldo insuficiente ou valor inválido"
    
    def exibir_saldo(self):
        return f"Saldo atual: R${self.saldo}"

Testando:

conta = ContaBancaria("João", 500)
print(conta.banco)  # Banco do Brasil
print(conta.exibir_saldo())  # Saldo atual: R$500
conta.depositar(150)
print(conta.exibir_saldo())  # Saldo atual: R$650
conta.sacar(200)
print(conta.exibir_saldo())  # Saldo atual: R$450

Note que banco é um atributo de classe e pode ser acessado tanto via instância (conta.banco) quanto via classe (ContaBancaria.banco).

Instanciando

Instanciar é o processo de criar um objeto a partir de uma classe. Para isso, chamamos a classe como se fosse uma função, passando os argumentos necessários para o __init__. O resultado é uma instância (objeto) que possui seus próprios atributos e métodos.

Vejamos mais exemplos:

class Carro:
    def __init__(self, marca, modelo, ano):
        self.marca = marca
        self.modelo = modelo
        self.ano = ano
        self.velocidade = 0
    
    def acelerar(self, incremento):
        self.velocidade += incremento
        return f"Velocidade atual: {self.velocidade} km/h"
    
    def frear(self, decremento):
        self.velocidade = max(0, self.velocidade - decremento)
        return f"Velocidade atual: {self.velocidade} km/h"

# Criando objetos (instâncias)
carro1 = Carro("Toyota", "Corolla", 2020)
carro2 = Carro("Honda", "Civic", 2022)

print(carro1.marca)  # Toyota
print(carro2.marca)  # Honda
print(carro1.acelerar(30))  # Velocidade atual: 30 km/h
print(carro2.acelerar(50))  # Velocidade atual: 50 km/h

Cada objeto criado é independente: alterar atributos de um não afeta o outro. Isso é fundamental na POO.

Podemos também verificar o tipo de um objeto e acessar seus atributos dinamicamente:

print(type(carro1))  # <class '__main__.Carro'>
print(isinstance(carro1, Carro))  # True

Agora que você entende como criar e usar classes, vamos explorar boas práticas e exercícios para fixar o conteúdo.

Boas práticas

  • Use nomes de classes em CamelCase (ex.: MinhaClasse).
  • Sempre inclua o método __init__ para inicializar atributos, mesmo que seja vazio.
  • Utilize self para acessar atributos e métodos dentro da classe.
  • Mantenha a coesão: cada classe deve ter uma responsabilidade única.
  • Prefira atributos privados (com dois underscores) quando não quiser acesso externo direto, mas lembre-se de que em Python isso é uma convenção, não uma restrição rígida.
  • Documente suas classes e métodos com docstrings.

Exercícios

  1. Crie uma classe Retangulo com atributos largura e altura (float), um método area() que retorna a área, e um método perimetro() que retorna o perímetro. Instancie um retângulo de 5x3 e imprima área e perímetro.

    ✓ Resposta:
    class Retangulo:
        def __init__(self, largura, altura):
            self.largura = largura
            self.altura = altura
        
        def area(self):
            return self.largura * self.altura
        
        def perimetro(self):
            return 2 * (self.largura + self.altura)
    
    ret = Retangulo(5, 3)
    print(f"Área: {ret.area()}")  # 15
    print(f"Perímetro: {ret.perimetro()}")  # 16
  2. Defina uma classe Livro com título, autor e número de páginas. Adicione um método descricao() que retorna uma string formatada. Crie dois livros e imprima as descrições.

    ✓ Resposta:
    class Livro:
        def __init__(self, titulo, autor, paginas):
            self.titulo = titulo
            self.autor = autor
            self.paginas = paginas
        
        def descricao(self):
            return f"'{self.titulo}' por {self.autor} - {self.paginas} páginas"
    
    livro1 = Livro("Dom Casmurro", "Machado de Assis", 256)
    livro2 = Livro("1984", "George Orwell", 328)
    print(livro1.descricao())
    print(livro2.descricao())
  3. Crie uma classe Pessoa com nome, idade e um método fazer_aniversario() que incrementa a idade em 1. Instancie uma pessoa, chame o método e mostre a nova idade.

    ✓ Resposta:
    class Pessoa:
        def __init__(self, nome, idade):
            self.nome = nome
            self.idade = idade
        
        def fazer_aniversario(self):
            self.idade += 1
            return f"Parabéns, {self.nome}! Agora você tem {self.idade} anos."
    
    p = Pessoa("Maria", 30)
    print(p.fazer_aniversario())  # Parabéns, Maria! Agora você tem 31 anos.
  4. Crie uma classe ContaBancaria com titular, saldo inicial (0 por padrão), métodos depositar(valor) e sacar(valor). Adicione uma validação para não deixar saldo negativo. Teste com depósitos e saques.

    ✓ Resposta:
    class ContaBancaria:
        def __init__(self, titular, saldo=0):
            self.titular = titular
            self.saldo = saldo
        
        def depositar(self, valor):
            if valor > 0:
                self.saldo += valor
                return f"Depósito de R${valor:.2f} realizado. Saldo: R${self.saldo:.2f}"
            return "Valor de depósito inválido"
        
        def sacar(self, valor):
            if 0 < valor <= self.saldo:
                self.saldo -= valor
                return f"Saque de R${valor:.2f} realizado. Saldo: R${self.saldo:.2f}"
            return "Saldo insuficiente ou valor inválido"
    
    conta = ContaBancaria("Carlos", 1000)
    print(conta.depositar(200))  # Depósito de R$200.00 realizado. Saldo: R$1200.00
    print(conta.sacar(1500))  # Saldo insuficiente ou valor inválido
    print(conta.sacar(500))  # Saque de R$500.00 realizado. Saldo: R$700.00
  5. Crie uma classe Aluno com nome, matrícula e uma lista de notas. Adicione um método media() que retorna a média das notas e um método situacao() que retorna "Aprovado" se média ≥ 7, "Recuperação" se média ≥ 4 e < 7, e "Reprovado" se média < 4. Instancie um aluno com notas e imprima a situação.

    ✓ Resposta:
    class Aluno:
        def __init__(self, nome, matricula, notas):
            self.nome = nome
            self.matricula = matricula
            self.notas = notas
        
        def media(self):
            if not self.notas:
                return 0
            return sum(self.notas) / len(self.notas)
        
        def situacao(self):
            media = self.media()
            if media >= 7:
                return "Aprovado"
            elif media >= 4:
                return "Recuperação"
            else:
                return "Reprovado"
    
    aluno = Aluno("Beatriz", "2021001", [8, 7.5, 9])
    print(f"Média: {aluno.media():.2f}")
    print(f"Situação: {aluno.situacao()}")

Referências