Declarando uma classe: estrutura mínima
Uma classe define um “molde” com dados (atributos) e comportamentos (métodos). Em Python, você declara uma classe com class e define métodos como funções dentro dela.
class ContaBancaria:
passNa prática, quase sempre você vai incluir um construtor (__init__) para inicializar o estado do objeto.
Passo a passo: criando uma classe com estado inicial
Vamos criar uma conta com titular e saldo inicial. Repare no parâmetro self e no uso de self.atributo para guardar dados no objeto.
class ContaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self.saldo = saldo_inicial
c1 = ContaBancaria("Ana", 100)
print(c1.titular) # Ana
print(c1.saldo) # 100O que é o parâmetro self (e por que ele é obrigatório)
self é uma referência ao próprio objeto que está executando o método. Quando você chama c1.saldo ou c1 executa um método, é esse objeto que deve ser acessado dentro do método. Por convenção, o primeiro parâmetro de métodos de instância se chama self.
Ao chamar um método, Python passa o objeto automaticamente:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
c1.depositar(50)equivale aContaBancaria.depositar(c1, 50)
class ContaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self.saldo = saldo_inicial
def depositar(self, valor):
self.saldo += valor
c1 = ContaBancaria("Ana", 100)
c1.depositar(50)
print(c1.saldo) # 150Atributos de instância vs atributos de classe
Atributos de instância
São guardados em cada objeto individualmente. Você normalmente os cria no __init__ usando self. Cada instância tem seus próprios valores.
class Usuario:
def __init__(self, nome):
self.nome = nome
u1 = Usuario("Ana")
u2 = Usuario("Bruno")
print(u1.nome) # Ana
print(u2.nome) # BrunoAtributos de classe
São definidos diretamente no corpo da classe e pertencem à classe como um todo. Eles são compartilhados por todas as instâncias (a menos que você sobrescreva em uma instância específica).
class Usuario:
especie = "Humano" # atributo de classe
def __init__(self, nome):
self.nome = nome # atributo de instância
u1 = Usuario("Ana")
u2 = Usuario("Bruno")
print(u1.especie) # Humano
print(u2.especie) # HumanoCuidado: atributo de classe mutável (armadilha comum)
Se o atributo de classe for mutável (lista, dicionário, conjunto), todas as instâncias podem acabar compartilhando o mesmo objeto, causando efeitos colaterais difíceis de perceber.
class Carrinho:
itens = [] # armadilha: lista compartilhada
def adicionar(self, item):
self.itens.append(item)
c1 = Carrinho()
c2 = Carrinho()
c1.adicionar("maçã")
print(c2.itens) # ['maçã'] (surpresa!)Para que cada instância tenha sua própria lista, crie o atributo no __init__:
class Carrinho:
def __init__(self):
self.itens = [] # lista por instância
def adicionar(self, item):
self.itens.append(item)Métodos que operam no estado interno: consultas vs comandos
Um padrão útil é separar métodos em dois tipos:
- Consultas (queries): retornam informações e não alteram o estado do objeto.
- Comandos (commands): alteram o estado do objeto (e geralmente retornam
None).
Exemplo prático: Conta com consultas e comandos
class ContaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self.saldo = saldo_inicial
# Consulta: não altera estado
def saldo_atual(self):
return self.saldo
# Comando: altera estado
def depositar(self, valor):
self._validar_valor_positivo(valor)
self.saldo += valor
# Comando: altera estado
def sacar(self, valor):
self._validar_valor_positivo(valor)
if valor > self.saldo:
raise ValueError("Saldo insuficiente")
self.saldo -= valor
# Método utilitário interno (detalhe de implementação)
def _validar_valor_positivo(self, valor):
if not isinstance(valor, (int, float)):
raise TypeError("Valor deve ser numérico")
if valor <= 0:
raise ValueError("Valor deve ser positivo")
c = ContaBancaria("Ana", 100)
print(c.saldo_atual()) # 100
c.depositar(50)
print(c.saldo_atual()) # 150Note o uso de _validar_valor_positivo como um método “utilitário” do objeto. O prefixo _ é uma convenção para indicar que é um detalhe interno e não faz parte da interface pública principal.
Evitando efeitos colaterais desnecessários
Efeito colateral é quando um método altera algo além do que o nome/contrato do método sugere. Alguns cuidados comuns:
- Consultas não devem modificar atributos.
- Evite alterar argumentos mutáveis recebidos (listas/dicts) sem deixar isso explícito.
- Prefira retornar novos valores/estruturas quando a intenção for “calcular”, e não “atualizar”.
Exemplo: consulta que acidentalmente altera estado (evite)
class Termometro:
def __init__(self):
self.leituras = []
def media(self):
# ruim: muda o estado ao "consultar"
self.leituras.append(0)
return sum(self.leituras) / len(self.leituras)Melhor: manter a consulta pura e deixar inserção de leitura em um comando separado.
class Termometro:
def __init__(self):
self.leituras = []
def registrar(self, valor):
self.leituras.append(valor)
def media(self):
if not self.leituras:
return None
return sum(self.leituras) / len(self.leituras)Quando usar métodos de instância vs métodos utilitários
Métodos de instância
Use quando o comportamento depende do estado do objeto (self) ou quando faz sentido que a ação pertença àquele “tipo” de objeto.
conta.depositar(valor)alteraself.saldocarrinho.adicionar(item)alteraself.itens
Métodos utilitários (funções auxiliares)
Se uma lógica não depende do estado do objeto, pode ser melhor como função fora da classe (ou como método estático). Isso reduz acoplamento e facilita testes.
Exemplo: validar CPF, formatar texto, calcular imposto a partir de um número, etc. Se não usa self, pergunte: “isso realmente pertence ao objeto?”
def normalizar_nome(nome):
return " ".join(parte.capitalize() for parte in nome.split())
class Usuario:
def __init__(self, nome):
self.nome = normalizar_nome(nome)
u = Usuario("ana maria")
print(u.nome) # Ana MariaSe você quiser manter dentro da classe por organização, pode usar @staticmethod (não recebe self):
class Usuario:
def __init__(self, nome):
self.nome = self.normalizar_nome(nome)
@staticmethod
def normalizar_nome(nome):
return " ".join(parte.capitalize() for parte in nome.split())Passo a passo prático: implementando um objeto com operações típicas
Agora você vai construir uma classe com operações comuns: adicionar/remover itens, atualizar estado e validar entradas. O exemplo abaixo é um “estoque” simples.
1) Defina o estado (atributos de instância)
class Estoque:
def __init__(self):
self._itens = {} # {nome: quantidade}2) Crie comandos para alterar o estado (adicionar/remover/atualizar)
class Estoque:
def __init__(self):
self._itens = {}
def adicionar(self, nome, quantidade=1):
self._validar_nome(nome)
self._validar_quantidade(quantidade)
self._itens[nome] = self._itens.get(nome, 0) + quantidade
def remover(self, nome, quantidade=1):
self._validar_nome(nome)
self._validar_quantidade(quantidade)
atual = self._itens.get(nome, 0)
if atual == 0:
raise KeyError(f"Item '{nome}' não existe no estoque")
if quantidade > atual:
raise ValueError("Quantidade para remover excede o disponível")
novo = atual - quantidade
if novo == 0:
del self._itens[nome]
else:
self._itens[nome] = novo
def atualizar(self, nome, nova_quantidade):
self._validar_nome(nome)
self._validar_quantidade(nova_quantidade)
if nova_quantidade == 0:
self._itens.pop(nome, None)
else:
self._itens[nome] = nova_quantidade
def _validar_nome(self, nome):
if not isinstance(nome, str) or not nome.strip():
raise ValueError("Nome do item deve ser uma string não vazia")
def _validar_quantidade(self, quantidade):
if not isinstance(quantidade, int):
raise TypeError("Quantidade deve ser um inteiro")
if quantidade < 0:
raise ValueError("Quantidade não pode ser negativa")3) Crie consultas para observar o impacto no objeto
Consultas ajudam a “enxergar” o estado sem alterá-lo.
class Estoque:
def __init__(self):
self._itens = {}
# ... comandos e validações ...
def quantidade(self, nome):
self._validar_nome(nome)
return self._itens.get(nome, 0)
def listar(self):
# retorna uma cópia para evitar que código externo altere o dicionário interno
return dict(self._itens)Repare no detalhe: listar retorna uma cópia. Isso evita que alguém faça estoque.listar()["arroz"] = 999 e altere o estado interno sem passar pelas validações.
4) Teste o comportamento e observe o estado mudando
e = Estoque()
e.adicionar("arroz", 2)
e.adicionar("feijão", 1)
print(e.listar()) # {'arroz': 2, 'feijão': 1}
print(e.quantidade("arroz")) # 2
e.remover("arroz", 1)
print(e.listar()) # {'arroz': 1, 'feijão': 1}
e.atualizar("feijão", 0)
print(e.listar()) # {'arroz': 1}Tarefas práticas (para implementar e observar o impacto no objeto)
Tarefa 1: Carrinho de compras com validações
- Crie uma classe
Carrinhocomself.itens(lista de strings) ou um dicionário{produto: quantidade}. - Implemente comandos:
adicionar(produto, qtd),remover(produto, qtd),limpar(). - Implemente consultas:
total_itens()elistar()(retornando cópia). - Valide: produto não vazio; qtd inteira e positiva; não permitir remover mais do que existe.
Tarefa 2: Controle de progresso (estado que evolui)
- Crie
ProgressoCursocom atributos de instância:total_aulas,assistidas. - Comandos:
marcar_assistida(qtd=1)(não ultrapassar total),reiniciar(). - Consultas:
percentual()(0 a 100),faltam(). - Valide entradas e garanta que consultas não alterem estado.
Tarefa 3: Conta com regras (comandos que podem falhar)
- Expanda
ContaBancariapara terlimite(cheque especial) e permitir saldo negativo até-limite. - Implemente
sacarcom validação: não permitir ultrapassar o limite. - Adicione uma consulta
disponivel_para_saque()que retorne quanto ainda pode sacar. - Garanta que erros sejam comunicados com exceções apropriadas (
ValueError,TypeError).
Tarefa 4: Identificando atributos de classe corretamente
- Crie uma classe
Produtocom atributo de classetaxa_imposto(ex.: 0.1). - Cada instância tem
nomeepreco(atributos de instância). - Implemente uma consulta
preco_com_imposto()que useProduto.taxa_imposto(ouself.__class__.taxa_imposto). - Teste alterando
Produto.taxa_impostoe observe o impacto em todas as instâncias.
| Elemento | Onde fica | Exemplo | Impacto |
|---|---|---|---|
| Atributo de instância | No objeto (self) | self.saldo | Muda por instância |
| Atributo de classe | Na classe | Produto.taxa_imposto | Compartilhado entre instâncias |
| Método de instância | Recebe self | depositar(self, valor) | Opera no estado do objeto |
| Função utilitária | Fora da classe (ou @staticmethod) | normalizar_nome(nome) | Não depende do estado |