Orientação a objetos (OOP) é um modelo de organização de código onde dados e comportamentos ficam juntos em classes. Para projetos de análise de dados e automação que crescem além de scripts simples — múltiplos conectores, pipelines reutilizáveis, lógica de negócio compartilhada — OOP torna o código mais fácil de manter, testar e estender.
Classe básica e __init__
class RelatorioVendas:
def __init__(self, arquivo, regiao):
self.arquivo = arquivo
self.regiao = regiao
self.df = None # será carregado depois
def carregar(self):
import pandas as pd
self.df = pd.read_csv(self.arquivo)
return self # retornar self permite encadear chamadas
def filtrar_regiao(self):
self.df = self.df[self.df["regiao"] == self.regiao]
return self
def total(self):
return self.df["valor"].sum()
# Uso:
rel = RelatorioVendas("vendas.csv", "Sul")
total = rel.carregar().filtrar_regiao().total()
print(f"Total Sul: R$ {total:,.2f}")
Métodos especiais (dunder)
class Pedido:
def __init__(self, id_pedido, cliente, valor):
self.id_pedido = id_pedido
self.cliente = cliente
self.valor = valor
def __repr__(self):
return f"Pedido(id={self.id_pedido}, cliente='{self.cliente}', valor={self.valor})"
def __str__(self):
return f"Pedido #{self.id_pedido} — {self.cliente}: R$ {self.valor:,.2f}"
def __eq__(self, outro):
return self.id_pedido == outro.id_pedido
def __lt__(self, outro):
return self.valor < outro.valor
pedidos = [Pedido(3, "Carlos", 800), Pedido(1, "Ana", 1500), Pedido(2, "Bruno", 300)]
for p in sorted(pedidos): # usa __lt__
print(p)
Encapsulamento e propriedades
class Contrato:
def __init__(self, valor):
self._valor = valor # convenção: _prefixo = "interno"
@property
def valor(self):
return self._valor
@valor.setter
def valor(self, novo):
if novo < 0:
raise ValueError("Valor do contrato não pode ser negativo")
self._valor = novo
@property
def valor_com_imposto(self):
return self._valor * 1.15 # propriedade calculada, sem setter
c = Contrato(10000)
print(c.valor_com_imposto) # 11500.0
c.valor = -100 # raises ValueError
Herança
class Conector:
def __init__(self, nome):
self.nome = nome
def conectar(self):
raise NotImplementedError("Subclasse deve implementar conectar()")
def extrair(self, query):
raise NotImplementedError
class ConectorSQL(Conector):
def __init__(self, url):
super().__init__("SQL")
from sqlalchemy import create_engine
self.engine = create_engine(url)
def conectar(self):
print(f"Conectado ao banco SQL: {self.engine.url}")
return self
def extrair(self, query):
import pandas as pd
return pd.read_sql(query, self.engine)
class ConectorCSV(Conector):
def __init__(self, pasta):
super().__init__("CSV")
self.pasta = pasta
def conectar(self):
print(f"Pasta CSV: {self.pasta}")
return self
def extrair(self, arquivo):
import pandas as pd
return pd.read_csv(f"{self.pasta}/{arquivo}")
Quando NÃO usar OOP
Para scripts de análise de 50-100 linhas que rodam uma vez, funções simples são mais diretas. OOP compensa quando: o mesmo conceito aparece em vários lugares do código, o projeto tem mais de uma pessoa, ou você precisa testar partes isoladas. Não force OOP por "parecer profissional" — o melhor código é o mais simples que resolve o problema.
Perguntas frequentes
@staticmethod vs @classmethod: qual a diferença?
@staticmethod é uma função que vive dentro da classe por organização, mas não acessa nem self (instância) nem cls (classe). @classmethod recebe cls como primeiro argumento — útil para factory methods como Pedido.from_dict(dados) que criam instâncias de formas alternativas.
Devo usar dataclasses em vez de classes normais?
Para classes que são principalmente contêineres de dados (sem muita lógica), sim: @dataclass gera automaticamente __init__, __repr__ e __eq__. Use from dataclasses import dataclass e adicione @dataclass antes da definição da classe.