POO em Python: Guia Prático Completo
Domine Programação Orientada a Objetos em Python: classes, objetos, herança, polimorfismo e encapsulamento com exemplos práticos do mundo real.
Programação Orientada a Objetos (POO) é um paradigma fundamental que todo desenvolvedor Python precisa dominar. Neste guia, a gente vai explorar cada conceito com exemplos práticos e realistas para você aplicar nos seus projetos.
O que é POO?
POO é uma forma de organizar código em torno de objetos — estruturas que combinam dados (atributos) e comportamentos (métodos). Em vez de pensar em sequências de instruções, você modela o mundo real em classes e objetos.
Classes e Objetos
Uma classe é o molde; um objeto é a instância criada a partir desse molde.
class Produto:
"""Representa um produto do estoque."""
def __init__(self, nome, preco, quantidade=0):
self.nome = nome
self.preco = preco
self.quantidade = quantidade
def valor_estoque(self):
"""Calcula o valor total em estoque."""
return self.preco * self.quantidade
def vender(self, qtd):
"""Realiza uma venda, atualizando o estoque."""
if qtd > self.quantidade:
raise ValueError(
f"Estoque insuficiente! Disponível: {self.quantidade}"
)
self.quantidade -= qtd
total = self.preco * qtd
print(f"Venda realizada: {qtd}x {self.nome} = R${total:.2f}")
return total
def repor(self, qtd):
"""Repõe itens no estoque."""
self.quantidade += qtd
print(f"Reposição: +{qtd} unidades de {self.nome}")
def __str__(self):
return f"{self.nome} - R${self.preco:.2f} ({self.quantidade} em estoque)"
def __repr__(self):
return f"Produto('{self.nome}', {self.preco}, {self.quantidade})"
# Criando objetos
notebook = Produto("Notebook Dell", 3500, 25)
mouse = Produto("Mouse Logitech", 89.90, 150)
print(notebook) # Notebook Dell - R$3500.00 (25 em estoque)
notebook.vender(3) # Venda realizada: 3x Notebook Dell = R$10500.00
print(f"Valor em estoque: R${notebook.valor_estoque():,.2f}")
Atributos de Classe vs Instância
class Funcionario:
# Atributo de classe (compartilhado por todos)
empresa = "Python Brasil"
total_funcionarios = 0
salario_minimo = 1412.00
def __init__(self, nome, cargo, salario):
# Atributos de instância (únicos por objeto)
self.nome = nome
self.cargo = cargo
self.salario = salario
self.ativo = True
Funcionario.total_funcionarios += 1
@classmethod
def criar_estagiario(cls, nome):
"""Factory method para criar estagiários."""
return cls(nome, "Estagiário", cls.salario_minimo)
@staticmethod
def calcular_inss(salario):
"""Calcula o desconto do INSS (simplificado)."""
if salario <= 1412.00:
return salario * 0.075
elif salario <= 2666.68:
return salario * 0.09
elif salario <= 4000.03:
return salario * 0.12
else:
return salario * 0.14
def salario_liquido(self):
inss = self.calcular_inss(self.salario)
return self.salario - inss
def __str__(self):
return f"{self.nome} ({self.cargo}) - {self.empresa}"
# Uso
dev = Funcionario("Maria", "Desenvolvedora Python", 8500)
estagiario = Funcionario.criar_estagiario("João")
print(dev)
print(f"Salário líquido: R${dev.salario_liquido():,.2f}")
print(f"Total de funcionários: {Funcionario.total_funcionarios}")
Encapsulamento
Encapsulamento é o princípio de proteger os dados internos de um objeto. Python usa convenções para indicar o nível de acesso:
class ContaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular # público
self._agencia = "0001" # protegido (convenção)
self.__saldo = saldo_inicial # privado (name mangling)
self.__historico = []
@property
def saldo(self):
"""Getter para o saldo (somente leitura)."""
return self.__saldo
@property
def extrato(self):
"""Retorna o extrato da conta."""
return self.__historico.copy()
def depositar(self, valor):
if valor <= 0:
raise ValueError("O valor do depósito deve ser positivo.")
self.__saldo += valor
self.__registrar("DEPÓSITO", valor)
return self
def sacar(self, valor):
if valor <= 0:
raise ValueError("O valor do saque deve ser positivo.")
if valor > self.__saldo:
raise ValueError("Saldo insuficiente.")
self.__saldo -= valor
self.__registrar("SAQUE", -valor)
return self
def __registrar(self, tipo, valor):
"""Método privado para registrar transações."""
from datetime import datetime
self.__historico.append({
"tipo": tipo,
"valor": valor,
"saldo_apos": self.__saldo,
"data": datetime.now().isoformat(),
})
def imprimir_extrato(self):
print(f"\n{'=' * 45}")
print(f" EXTRATO - {self.titular}")
print(f"{'=' * 45}")
for t in self.__historico:
sinal = "+" if t["valor"] > 0 else ""
print(f" {t['tipo']:12s} {sinal}R${abs(t['valor']):>10,.2f}")
print(f"{'=' * 45}")
print(f" SALDO: R${self.__saldo:>10,.2f}")
print(f"{'=' * 45}")
# Uso
conta = ContaBancaria("Ana Silva", 1000)
conta.depositar(2500).depositar(800).sacar(350)
conta.imprimir_extrato()
# Tentando acessar diretamente:
print(conta.saldo) # OK - via property
# print(conta.__saldo) # AttributeError! - privado
Herança
Herança permite criar novas classes baseadas em classes existentes, reutilizando código.
class Veiculo:
def __init__(self, marca, modelo, ano, cor):
self.marca = marca
self.modelo = modelo
self.ano = ano
self.cor = cor
self.velocidade = 0
self.ligado = False
def ligar(self):
if not self.ligado:
self.ligado = True
print(f"{self.modelo} ligado!")
else:
print(f"{self.modelo} já está ligado.")
def desligar(self):
if self.ligado:
self.velocidade = 0
self.ligado = False
print(f"{self.modelo} desligado!")
def acelerar(self, incremento=10):
if not self.ligado:
print("Ligue o veículo primeiro!")
return
self.velocidade += incremento
print(f"{self.modelo}: {self.velocidade} km/h")
def __str__(self):
estado = "ligado" if self.ligado else "desligado"
return f"{self.marca} {self.modelo} ({self.ano}) - {estado}"
class Carro(Veiculo):
def __init__(self, marca, modelo, ano, cor, portas=4):
super().__init__(marca, modelo, ano, cor)
self.portas = portas
self.porta_malas_aberto = False
def abrir_porta_malas(self):
self.porta_malas_aberto = True
print("Porta-malas aberto!")
def acelerar(self, incremento=10):
"""Override: limita velocidade a 200 km/h."""
if not self.ligado:
print("Ligue o carro primeiro!")
return
self.velocidade = min(self.velocidade + incremento, 200)
print(f"{self.modelo}: {self.velocidade} km/h")
class Moto(Veiculo):
def __init__(self, marca, modelo, ano, cor, cilindradas=150):
super().__init__(marca, modelo, ano, cor)
self.cilindradas = cilindradas
def empinar(self):
if self.velocidade > 30:
print("Empinando! (não faça isso na rua)")
else:
print("Velocidade insuficiente para empinar.")
class CarroEletrico(Carro):
def __init__(self, marca, modelo, ano, cor, bateria_kwh=75):
super().__init__(marca, modelo, ano, cor)
self.bateria_kwh = bateria_kwh
self.carga_percentual = 100
def carregar(self):
self.carga_percentual = 100
print(f"{self.modelo}: bateria carregada 100%!")
def acelerar(self, incremento=10):
if self.carga_percentual <= 0:
print("Bateria descarregada! Recarregue.")
return
super().acelerar(incremento)
self.carga_percentual -= 2
print(f" Bateria: {self.carga_percentual}%")
# Uso
carro = Carro("Volkswagen", "Polo", 2024, "Prata")
carro.ligar()
carro.acelerar(50)
carro.acelerar(80)
print()
tesla = CarroEletrico("Tesla", "Model 3", 2025, "Branco", 82)
tesla.ligar()
tesla.acelerar(60)
Polimorfismo
Polimorfismo permite que objetos de diferentes classes respondam ao mesmo método de formas diferentes.
from abc import ABC, abstractmethod
class FormaPagamento(ABC):
"""Classe abstrata para formas de pagamento."""
@abstractmethod
def processar(self, valor):
pass
@abstractmethod
def descricao(self):
pass
class Pix(FormaPagamento):
def __init__(self, chave):
self.chave = chave
def processar(self, valor):
print(f"Pagamento de R${valor:.2f} via PIX")
print(f" Chave: {self.chave}")
print(f" Desconto de 5%: R${valor * 0.95:.2f}")
return valor * 0.95
def descricao(self):
return f"PIX ({self.chave})"
class CartaoCredito(FormaPagamento):
def __init__(self, numero, parcelas=1):
self.numero = f"****{numero[-4:]}"
self.parcelas = parcelas
def processar(self, valor):
if self.parcelas > 1:
valor_parcela = valor / self.parcelas
print(f"Pagamento de R${valor:.2f} no cartão de crédito")
print(f" {self.parcelas}x de R${valor_parcela:.2f}")
else:
print(f"Pagamento de R${valor:.2f} no cartão (à vista)")
return valor
def descricao(self):
return f"Cartão {self.numero} ({self.parcelas}x)"
class Boleto(FormaPagamento):
def processar(self, valor):
desconto = valor * 0.03
print(f"Boleto gerado: R${valor - desconto:.2f}")
print(f" Desconto de 3%: -R${desconto:.2f}")
return valor - desconto
def descricao(self):
return "Boleto Bancário"
def realizar_compra(valor, pagamento: FormaPagamento):
"""Função que aceita qualquer forma de pagamento (polimorfismo)."""
print(f"\nProcessando compra de R${valor:.2f}")
print(f"Método: {pagamento.descricao()}")
total = pagamento.processar(valor)
print(f"Valor final cobrado: R${total:.2f}")
return total
# Uso - mesma função, comportamentos diferentes
compra_pix = realizar_compra(1000, Pix("email@email.com"))
compra_cartao = realizar_compra(1000, CartaoCredito("1234567890123456", 3))
compra_boleto = realizar_compra(1000, Boleto())
Métodos Mágicos (Dunder Methods)
Python possui métodos especiais que permitem customizar o comportamento dos seus objetos:
class Dinheiro:
"""Classe para representar valores monetários em Real."""
def __init__(self, valor):
self._centavos = round(valor * 100)
@property
def valor(self):
return self._centavos / 100
def __add__(self, outro):
if isinstance(outro, Dinheiro):
return Dinheiro(self.valor + outro.valor)
return Dinheiro(self.valor + outro)
def __sub__(self, outro):
if isinstance(outro, Dinheiro):
return Dinheiro(self.valor - outro.valor)
return Dinheiro(self.valor - outro)
def __mul__(self, fator):
return Dinheiro(self.valor * fator)
def __eq__(self, outro):
if isinstance(outro, Dinheiro):
return self._centavos == outro._centavos
return False
def __lt__(self, outro):
if isinstance(outro, Dinheiro):
return self._centavos < outro._centavos
return NotImplemented
def __le__(self, outro):
return self == outro or self < outro
def __str__(self):
return f"R${self.valor:,.2f}"
def __repr__(self):
return f"Dinheiro({self.valor})"
def __bool__(self):
return self._centavos != 0
# Uso
preco = Dinheiro(99.90)
desconto = Dinheiro(15.00)
quantidade = 3
subtotal = preco * quantidade
total = subtotal - desconto
print(f"Preço unitário: {preco}")
print(f"Subtotal ({quantidade}x): {subtotal}")
print(f"Desconto: -{desconto}")
print(f"Total: {total}")
print(f"É mais barato que R$300? {total < Dinheiro(300)}")
Composição vs Herança
Nem sempre herança é a melhor solução. Muitas vezes, composição (ter objetos dentro de outros objetos) é preferível:
class Motor:
def __init__(self, tipo, potencia_cv):
self.tipo = tipo
self.potencia_cv = potencia_cv
self.ligado = False
def ligar(self):
self.ligado = True
def desligar(self):
self.ligado = False
def __str__(self):
return f"Motor {self.tipo} {self.potencia_cv}cv"
class GPS:
def __init__(self):
self.destino = None
def definir_destino(self, destino):
self.destino = destino
print(f"GPS: Rota calculada para {destino}")
def navegar(self):
if self.destino:
print(f"GPS: Navegando para {self.destino}...")
class CarroCompleto:
"""Usa composição em vez de herança múltipla."""
def __init__(self, marca, modelo, motor, tem_gps=True):
self.marca = marca
self.modelo = modelo
self.motor = motor # Composição
self.gps = GPS() if tem_gps else None # Composição
def ligar(self):
self.motor.ligar()
print(f"{self.modelo} ligado! ({self.motor})")
def ir_para(self, destino):
if self.gps:
self.gps.definir_destino(destino)
self.gps.navegar()
else:
print("GPS não disponível neste veículo.")
def __str__(self):
return f"{self.marca} {self.modelo} - {self.motor}"
# Uso
motor_turbo = Motor("Turbo", 200)
carro = CarroCompleto("VW", "Golf GTI", motor_turbo)
carro.ligar()
carro.ir_para("Praia de Copacabana")
Conclusão
POO é um dos pilares da programação moderna e Python oferece suporte completo a todos os seus conceitos. Os pontos-chave para lembrar são:
- Use classes para modelar entidades do mundo real
- Use encapsulamento para proteger dados internos
- Use herança quando existe uma relação “é um”
- Use composição quando existe uma relação “tem um”
- Use polimorfismo para código flexível e extensível
- Use métodos mágicos para objetos mais pythônicos
A prática é essencial. Tente modelar sistemas do seu dia a dia usando POO — um sistema de biblioteca, uma loja virtual, um gerenciador de tarefas — e os conceitos vão ficando cada vez mais naturais.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português