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.

7 min de leitura Equipe Python Brasil

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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português