Entendendo Classes e Objetos: O Fundamento da Programação Orientada a Objetos
Quando começamos a programar, frequentemente utilizamos variáveis simples e funções soltas para resolver problemas. No entanto, quando os projetos crescem e a complexidade aumenta, essa abordagem se torna caótica e difícil de manter. Classes e objetos são a resposta que a Programação Orientada a Objetos (POO) oferece para organizar código de forma estruturada, reutilizável e intuitiva.
Uma classe é um molde ou blueprint que define como um objeto deve ser estruturado. Um objeto é uma instância concreta dessa classe — é o resultado de usar esse molde. Pense em uma classe como a receita de um bolo e o objeto como o bolo propriamente dito. Você pode fazer vários bolos usando a mesma receita, cada um com suas características particulares. Em Python, as classes nos permitem encapsular dados (atributos) e comportamentos (métodos) em uma unidade coesa que modela entidades do mundo real.
A Estrutura Básica: Definindo Uma Classe
Criando sua primeira classe
Uma classe em Python é definida usando a palavra-chave class. A estrutura mais básica segue este padrão:
class Pessoa:
pass
Isso cria uma classe vazia. Para que ela seja útil, precisamos adicionar atributos e métodos. Os atributos são características (dados) e os métodos são ações (funções) que a classe pode executar.
class Carro:
def __init__(self, marca, modelo, ano):
self.marca = marca
self.modelo = modelo
self.ano = ano
def descrever(self):
return f"{self.ano} {self.marca} {self.modelo}"
def idade(self, ano_atual=2024):
return ano_atual - self.ano
Agora vamos criar instâncias dessa classe:
carro1 = Carro("Toyota", "Corolla", 2020)
carro2 = Carro("Honda", "Civic", 2018)
print(carro1.descrever()) # 2020 Toyota Corolla
print(carro2.idade()) # 6
O papel crucial do método __init__
O método __init__ é o construtor da classe — ele é automaticamente chamado quando você cria uma nova instância. Seu principal objetivo é inicializar os atributos do objeto com valores específicos passados como argumentos. Sem __init__, você teria que atribuir manualmente cada atributo após criar o objeto, o que seria trabalhoso e propenso a erros.
O primeiro parâmetro de qualquer método, incluindo __init__, é sempre self. Esse parâmetro especial representa a instância do objeto em questão. Quando você escreve self.marca = marca, está dizendo: "no objeto que estou criando, defina o atributo 'marca' com o valor passado como argumento".
class Produto:
def __init__(self, nome, preco, estoque=0):
self.nome = nome
self.preco = preco
self.estoque = estoque
self.desconto = 0 # Atributo com valor padrão
produto1 = Produto("Notebook", 3500.00, 5)
produto2 = Produto("Mouse", 50.00) # estoque não fornecido, usa padrão
print(f"{produto1.nome}: R$ {produto1.preco}")
print(f"Estoque de {produto2.nome}: {produto2.estoque}")
Atributos: Armazenando Dados no Objeto
Atributos de instância versus atributos de classe
Existem dois tipos de atributos em Python. Os atributos de instância são específicos de cada objeto e são definidos dentro de __init__. Os atributos de classe pertencem à classe como um todo e são compartilhados por todas as instâncias.
class Conta:
juros = 0.05 # Atributo de classe - compartilhado por todas as contas
def __init__(self, titular, saldo=0):
self.titular = titular # Atributo de instância
self.saldo = saldo # Atributo de instância
conta1 = Conta("Alice", 1000)
conta2 = Conta("Bob", 2000)
print(conta1.titular) # Alice
print(conta1.saldo) # 1000
print(Conta.juros) # 0.05 - acessado pela classe
# Cada instância tem seus próprios atributos de instância
print(conta1.juros) # 0.05 - acessa via instância
print(conta2.juros) # 0.05 - mesmo valor
Modificando atributos após a criação
Os atributos não são imutáveis após a criação. Você pode modificá-los a qualquer momento:
class Estudante:
def __init__(self, nome, matricula):
self.nome = nome
self.matricula = matricula
self.notas = []
def adicionar_nota(self, nota):
self.notas.append(nota)
def media(self):
return sum(self.notas) / len(self.notas) if self.notas else 0
aluno = Estudante("Carlos", "2024001")
aluno.adicionar_nota(7.5)
aluno.adicionar_nota(8.0)
aluno.adicionar_nota(9.5)
print(f"Média de {aluno.nome}: {aluno.media():.2f}") # 8.33
Métodos: Definindo Comportamentos
O que são métodos e como funcionam
Um método é uma função definida dentro de uma classe. Ele permite que objetos realizem ações e modifiquem seus próprios dados. Todo método recebe self como primeiro parâmetro, permitindo acesso aos atributos e outros métodos da instância.
class Banco:
def __init__(self, saldo_inicial=0):
self.saldo = saldo_inicial
def depositar(self, valor):
"""Adiciona valor ao saldo"""
if valor > 0:
self.saldo += valor
return f"Depósito de R$ {valor:.2f} realizado. Saldo: R$ {self.saldo:.2f}"
return "Valor deve ser positivo"
def sacar(self, valor):
"""Remove valor do saldo se houver fundos"""
if valor > self.saldo:
return f"Saldo insuficiente. Disponível: R$ {self.saldo:.2f}"
if valor > 0:
self.saldo -= valor
return f"Saque de R$ {valor:.2f} realizado. Saldo: R$ {self.saldo:.2f}"
return "Valor deve ser positivo"
def obter_saldo(self):
"""Retorna o saldo atual"""
return self.saldo
conta = Banco(500)
print(conta.depositar(200)) # Depósito de R$ 200.00 realizado. Saldo: R$ 700.00
print(conta.sacar(150)) # Saque de R$ 150.00 realizado. Saldo: R$ 550.00
print(f"Saldo final: R$ {conta.obter_saldo():.2f}")
Métodos especiais em Python
Python oferece vários métodos especiais (dunder methods) que começam e terminam com dois underscores. Eles permitem que seus objetos se comportem como objetos nativos do Python. Alguns dos mais importantes são:
class Livro:
def __init__(self, titulo, autor, paginas):
self.titulo = titulo
self.autor = autor
self.paginas = paginas
def __str__(self):
"""Retorna representação legível para humanos"""
return f"{self.titulo} por {self.autor}"
def __repr__(self):
"""Retorna representação técnica (útil para debug)"""
return f"Livro('{self.titulo}', '{self.autor}', {self.paginas})"
def __len__(self):
"""Permite usar len() no objeto"""
return self.paginas
def __eq__(self, outro):
"""Permite comparar dois livros com =="""
if not isinstance(outro, Livro):
return False
return self.titulo == outro.titulo and self.autor == outro.autor
livro1 = Livro("1984", "George Orwell", 328)
livro2 = Livro("1984", "George Orwell", 328)
print(str(livro1)) # 1984 por George Orwell
print(repr(livro1)) # Livro('1984', 'George Orwell', 328)
print(len(livro1)) # 328
print(livro1 == livro2) # True
Boas Práticas e Padrões Comuns
Encapsulamento com atributos privados
Em Python, não existe encapsulamento forçado como em outras linguagens, mas usa-se a convenção de prefixar nomes com underscore para indicar que um atributo é "privado" (não deve ser acessado diretamente de fora):
class Senha:
def __init__(self, valor):
self._valor = valor # Convenção: privado
def validar(self, tentativa):
return self._valor == tentativa
def mudar(self, antiga, nova):
if self._valor == antiga:
self._valor = nova
return "Senha alterada com sucesso"
return "Senha antiga incorreta"
conta = Senha("minhasenha123")
print(conta.validar("minhasenha123")) # True
print(conta.mudar("minhasenha123", "nova")) # Senha alterada com sucesso
Usando propriedades (properties) para controle de acesso
Para mais controle sobre como os atributos são acessados e modificados, use o decorador @property:
class Temperatura:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""Getter para celsius"""
return self._celsius
@celsius.setter
def celsius(self, valor):
"""Setter com validação"""
if valor < -273.15:
raise ValueError("Temperatura abaixo do zero absoluto")
self._celsius = valor
@property
def fahrenheit(self):
"""Propriedade calculada"""
return (self._celsius * 9/5) + 32
temp = Temperatura(25)
print(f"{temp.celsius}°C = {temp.fahrenheit}°F") # 25°C = 77.0°F
temp.celsius = 0
print(f"{temp.celsius}°C = {temp.fahrenheit}°F") # 0°C = 32.0°F
Um exemplo prático completo
Vamos criar uma classe que demonstra todos os conceitos aprendidos:
class Funcionario:
# Atributo de classe
empresa = "TechCorp"
salario_minimo = 1200
def __init__(self, nome, cargo, salario):
self.nome = nome
self.cargo = cargo
self._salario = salario
self.horas_trabalhadas = 0
@property
def salario(self):
return self._salario
@salario.setter
def salario(self, valor):
if valor < self.salario_minimo:
raise ValueError(f"Salário não pode ser menor que R$ {self.salario_minimo}")
self._salario = valor
def registrar_hora(self, horas):
self.horas_trabalhadas += horas
return f"{self.nome} registrou {horas} horas"
def calcular_bonus(self):
if self.horas_trabalhadas > 160:
return self._salario * 0.10
return 0
def __str__(self):
return f"{self.nome} - {self.cargo} (R$ {self._salario:.2f})"
def __repr__(self):
return f"Funcionario('{self.nome}', '{self.cargo}', {self._salario})"
# Criando instâncias
func1 = Funcionario("Ana", "Desenvolvedora", 5000)
func2 = Funcionario("Bruno", "Designer", 3500)
print(func1) # Ana - Desenvolvedora (R$ 5000.00)
print(func1.registrar_hora(40)) # Ana registrou 40 horas
print(f"Bônus: R$ {func1.calcular_bonus():.2f}")
# Tentando alterar salário
try:
func2.salario = 800 # Vai lançar exceção
except ValueError as e:
print(f"Erro: {e}")
Conclusão
Nesta aula, você compreendeu que classes e objetos são a base da Programação Orientada a Objetos em Python, permitindo modelar entidades do mundo real de forma intuitiva e organizada. O método __init__ é o construtor que inicializa seus objetos, eliminando a necessidade de configuração manual após instanciação — isso torna o código mais limpo e seguro. Finalmente, atributos e métodos trabalham juntos para encapsular dados e comportamentos, criando abstrações poderosas que facilitam manutenção, reutilização e escalabilidade de código.