POO Django
Orientação a Objetos — Tópicos Avançados para o Django¶
Este notebook complementa o roteiro básico de POO com os conceitos essenciais para trabalhar com o framework Django:
__repr__— representação para desenvolvedores@property— atributos calculados@classmethode@staticmethod— outros tipos de métodos- Encapsulamento — convenções
_e__ - Mixins — herança múltipla composicional
- Ponte POO → Model Django
1. __repr__: A representação para desenvolvedores¶
Você já aprendeu sobre __str__, que define como um objeto é exibido para o usuário final com print(). Existe um método irmão chamado __repr__, que define como o objeto é representado para o desenvolvedor — especialmente no shell interativo e em logs.
A diferença prática:
__str__→ legível para o usuário__repr__→ preciso e não ambíguo para o desenvolvedor
Conexão com Django: No shell do Django (
python manage.py shell), quando você fazCachorro.objects.get(id=1), o que aparece é o__repr__. Sem ele, você verá algo como<Dog: Dog object (1)>.
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
# Legível para o usuário final
return f"{self.name}, {self.age} anos"
def __repr__(self):
# Útil para o desenvolvedor: mostra como recriar o objeto
return f"Dog(name={self.name!r}, age={self.age!r})"
miles = Dog("Miles", 4)
print(miles) # usa __str__
miles # no notebook/shell, usa __repr__
Miles, 4 anos
Dog(name='Miles', age=4)
# repr() força o uso de __repr__ explicitamente
print(repr(miles))
# Em listas, __repr__ é usado automaticamente
cachorros = [Dog("Miles", 4), Dog("Buddy", 9)]
print(cachorros)
Dog(name='Miles', age=4) [Dog(name='Miles', age=4), Dog(name='Buddy', age=9)]
2. @property: Atributos calculados¶
O decorador @property permite criar atributos que são calculados automaticamente a partir de outros atributos, mas acessados como se fossem atributos comuns (sem parênteses).
Conexão com Django: Models Django usam
@propertypara campos calculados que não são persistidos no banco de dados. Por exemplo:pedido.total,usuario.nome_completo,produto.preco_com_desconto.
class Produto:
def __init__(self, nome, preco, desconto):
self.nome = nome
self.preco = preco # atributo de instância normal
self.desconto = desconto # ex: 0.10 = 10%
@property
def preco_final(self):
# Calculado automaticamente — não fica armazenado
return self.preco * (1 - self.desconto)
def __str__(self):
return f"{self.nome}: R$ {self.preco_final:.2f}"
notebook = Produto("Notebook", 3000.00, 0.10)
# Acesso como atributo, não como método (sem parênteses)
print(notebook.preco_final)
print(notebook)
2700.0 Notebook: R$ 2700.00
# A grande vantagem: se preco ou desconto mudarem,
# preco_final é recalculado automaticamente
notebook.preco = 2500.00
print(notebook.preco_final) # reflete o novo valor automaticamente
2250.0
Exercício¶
Crie uma classe Funcionario com atributos nome, salario_base e bonus. Adicione uma @property chamada salario_total que retorna a soma do salário base com o bônus. Imprima o salário total de um funcionário.
class Funcionario:
def __init__(self, nome, salario_base, bonus):
self.nome = nome
self.salario_base = salario_base
self.bonus = bonus
@property
def salario_total(self):
return self.salario_base + self.bonus
def __str__(self):
return f"{self.nome} — Salário total: R$ {self.salario_total:,.2f}"
ana = Funcionario("Ana", 5000, 1200)
print(ana)
print(ana.salario_total)
Ana — Salário total: R$ 6,200.00 6200
3. @classmethod e @staticmethod¶
Além dos métodos de instância (que recebem self), Python oferece dois outros tipos de métodos:
@classmethod: recebe a própria classe como primeiro argumento (cls). Muito usado para criar factory methods — formas alternativas de construir um objeto.@staticmethod: não recebe nemselfnemcls. É uma função comum organizada dentro da classe por afinidade temática.
Conexão com Django:
Model.objects.create(...)funciona como um classmethod por baixo dos panos. Validações emForm.clean_campo()e métodos utilitários em views seguem o mesmo padrão.
class Funcionario:
salario_minimo = 1_412.00 # atributo de classe
def __init__(self, nome, salario):
self.nome = nome
self.salario = salario
# Factory method: cria um Funcionario com salário mínimo
@classmethod
def criar_com_salario_minimo(cls, nome):
return cls(nome, cls.salario_minimo)
# Método utilitário: não precisa de instância nem da classe
@staticmethod
def validar_cpf(cpf):
cpf_limpo = cpf.replace(".", "").replace("-", "")
return len(cpf_limpo) == 11 and cpf_limpo.isdigit()
def __str__(self):
return f"{self.nome} — R$ {self.salario:,.2f}"
# Criação via factory method (sem precisar saber o salário mínimo)
estagiario = Funcionario.criar_com_salario_minimo("Carlos")
print(estagiario)
# Validação sem precisar de uma instância
print(Funcionario.validar_cpf("123.456.789-09"))
print(Funcionario.validar_cpf("123"))
Resumo dos três tipos de métodos¶
| Tipo | Decorador | Primeiro parâmetro | Quando usar |
|---|---|---|---|
| Instância | (nenhum) | self | Acessa ou modifica dados do objeto |
| Classe | @classmethod | cls | Factory methods, construtores alternativos |
| Estático | @staticmethod | (nenhum) | Funções utilitárias relacionadas à classe |
4. Encapsulamento: Convenções _ e __¶
Em Python, não existem atributos verdadeiramente privados, mas há convenções amplamente respeitadas:
_atributo— protegido: indica "não use fora desta classe, mas pode em subclasses"__atributo— privado (name mangling): o Python renomeia internamente para_Classe__atributo, dificultando acesso externo acidental
Conexão com Django: O Django usa
_meta,_state,_deferredinternamente. Entender essas convenções evita que você sobrescreva atributos do framework sem querer.
class ContaBancaria:
def __init__(self, titular, saldo_inicial):
self.titular = titular # público
self._saldo = saldo_inicial # protegido (convenção)
self.__historico = [] # privado (name mangling)
def depositar(self, valor):
if valor > 0:
self._saldo += valor
self.__historico.append(f"Depósito: +R$ {valor:.2f}")
def sacar(self, valor):
if 0 < valor <= self._saldo:
self._saldo -= valor
self.__historico.append(f"Saque: -R$ {valor:.2f}")
else:
print("Saldo insuficiente.")
@property
def saldo(self):
return self._saldo
@property
def historico(self):
return list(self.__historico) # retorna cópia, não a lista original
def __str__(self):
return f"Conta de {self.titular} — Saldo: R$ {self._saldo:.2f}"
conta = ContaBancaria("Maria", 1000)
conta.depositar(500)
conta.sacar(200)
print(conta)
print(conta.saldo)
print(conta.historico)
# Tentando acessar o atributo "privado" diretamente
try:
print(conta.__historico)
except AttributeError as e:
print(f"Erro: {e}")
# O name mangling renomeia para _ContaBancaria__historico
print(conta._ContaBancaria__historico) # funciona, mas é má prática
5. Mixins: Herança Múltipla Composicional¶
Um Mixin é uma classe projetada para adicionar funcionalidades específicas a outras classes via herança múltipla, sem ser instanciada sozinha. A ideia é compor comportamentos em vez de criar hierarquias profundas.
Conexão com Django: Este é o padrão central das Class-Based Views (CBVs) do Django.
LoginRequiredMixin,PermissionRequiredMixin,FormMixinsão todos mixins que você combina com suas views. Sem entender mixins, as CBVs do Django são incompreensíveis.
import json
class JsonMixin:
"""Adiciona capacidade de serializar o objeto para JSON."""
def to_json(self):
return json.dumps(self.__dict__, ensure_ascii=False)
class LogMixin:
"""Adiciona capacidade de registrar criação do objeto."""
def log(self):
print(f"[LOG] Objeto '{self.__class__.__name__}' criado: {self}")
class ValidacaoMixin:
"""Adiciona validação básica de dados."""
def validar(self):
for attr, valor in self.__dict__.items():
if valor is None:
raise ValueError(f"Atributo '{attr}' não pode ser None")
return True
# Pedido combina múltiplos mixins
class Pedido(LogMixin, JsonMixin, ValidacaoMixin):
def __init__(self, id_pedido, produto, total):
self.id_pedido = id_pedido
self.produto = produto
self.total = total
def __str__(self):
return f"Pedido #{self.id_pedido} — {self.produto} — R$ {self.total:.2f}"
p = Pedido(42, "Notebook", 3500.00)
p.log()
print(p.to_json())
print(p.validar())
# A ordem dos mixins importa! Python usa MRO (Method Resolution Order)
# Para ver a ordem de resolução:
print(Pedido.__mro__)
6. Conectando os pontos: POO em Python → Model no Django¶
Tudo que você aprendeu neste roteiro é diretamente aplicado no Django. Um Model Django é uma classe Python que herda de models.Model. Veja a correspondência:
# ============================================================
# O que você aprendeu em POO puro:
# ============================================================
class Cachorro:
# Atributo de classe
especie = "Canis familiaris"
def __init__(self, nome, idade, raca):
# Atributos de instância
self.nome = nome
self.idade = idade
self.raca = raca
# Método mágico: representação amigável
def __str__(self):
return f"{self.nome} ({self.raca})"
# Método mágico: representação para desenvolvedor
def __repr__(self):
return f"Cachorro(nome={self.nome!r}, idade={self.idade!r})"
# Atributo calculado
@property
def eh_filhote(self):
return self.idade < 1
# Factory method
@classmethod
def criar_filhote(cls, nome, raca):
return cls(nome, 0, raca)
rex = Cachorro("Rex", 3, "Labrador")
print(rex)
print(repr(rex))
print(rex.eh_filhote)
bolt = Cachorro.criar_filhote("Bolt", "Dálmata")
print(bolt)
print(bolt.eh_filhote)
# ============================================================
# Como fica no Django (não executa aqui — apenas para estudo):
# ============================================================
django_model_exemplo = """
from django.db import models
class Cachorro(models.Model): # herda de models.Model (classe pai do Django)
# Atributos de classe especiais — definem as colunas do banco de dados
nome = models.CharField(max_length=100)
idade = models.IntegerField()
raca = models.CharField(max_length=100)
def __str__(self): # mesmo método mágico que você aprendeu!
return f\"{self.nome} ({self.raca})\"
def __repr__(self):
return f\"Cachorro(nome={self.nome!r}, idade={self.idade!r})\"
@property
def eh_filhote(self): # @property funciona igual
return self.idade < 1
@classmethod
def criar_filhote(cls, nome, raca): # @classmethod funciona igual
return cls.objects.create(nome=nome, idade=0, raca=raca)
class Meta: # inner class especial do Django
ordering = ['nome'] # ordena resultados por nome por padrão
verbose_name = 'Cachorro'
verbose_name_plural = 'Cachorros'
"""
print(django_model_exemplo)
Tabela de correspondência: POO Python → Django¶
| Conceito POO (Python puro) | Equivalente no Django |
|---|---|
class Cachorro: | class Cachorro(models.Model): |
Atributo de instância (self.nome) | Campo do banco: models.CharField(...) |
__str__ | __str__ (exibição no Admin e no shell) |
__repr__ | __repr__ (shell do Django) |
@property | @property (campos calculados, não persistidos) |
@classmethod | Manager methods / objects.create() |
Herança (class Filho(Pai)) | class MinhaView(LoginRequiredMixin, ListView) |
| Mixins | LoginRequiredMixin, PermissionRequiredMixin, etc. |
super() | super().save(), super().form_valid(), etc. |
Conclusão: aprender POO em Python puro é aprender a base do Django. Cada vez que você cria um Model, uma View baseada em classe ou um Form, você está usando herança,
super(),@property,__str__e mixins — exatamente o que você praticou aqui.