Desafio Python

Conta bancária com saldo e histórico

Enunciado

Implemente uma classe ContaBancaria com as properties saldo (somente leitura, calculado a partir do histórico de transações) e historico (somente leitura, retorna uma cópia da lista de transações). A classe deve ter métodos depositar(valor) e sacar(valor). Depósitos e saques devem ser registrados no histórico como tuplas (tipo, valor), onde tipo é 'deposito' ou 'saque'. Saques não podem exceder o saldo; caso contrário, lance ValueError('Saldo insuficiente'). Depósitos e saques devem ser maiores que zero; caso contrário, lance ValueError('Valor deve ser positivo').

Requisitos

  • A property saldo deve ser somente leitura e calcular o saldo atual somando depósitos e subtraindo saques do histórico.
  • A property historico deve ser somente leitura e retornar uma cópia da lista de transações.
  • Os métodos depositar e sacar devem validar o valor (maior que zero) e registrar a transação no histórico.
  • O saque não pode deixar o saldo negativo; caso contrário, lance ValueError('Saldo insuficiente').

Código inicial

class ContaBancaria:
    def __init__(self):
        self._transacoes = []

    @property
    def saldo(self):
        pass

    @property
    def historico(self):
        pass

    def depositar(self, valor):
        pass

    def sacar(self, valor):
        pass

Saída esperada

Saldo inicial: 0
Saldo após depósito: 100
Histórico: [('deposito', 100)]
Saldo após saque: 70
Histórico: [('deposito', 100), ('saque', 30)]
Erro: Saldo insuficiente
Erro: Valor deve ser positivo
Ver dica

No saldo, percorra self._transacoes e some os valores de depósitos, subtraia os de saques. No historico, retorne uma cópia com list(self._transacoes) para evitar modificações externas.

Mostrar solução
class ContaBancaria:
    def __init__(self):
        self._transacoes = []  # lista de tuplas (tipo, valor)

    @property
    def saldo(self):
        total = 0
        for tipo, valor in self._transacoes:
            if tipo == 'deposito':
                total += valor
            else:  # 'saque'
                total -= valor
        return total

    @property
    def historico(self):
        return list(self._transacoes)  # cópia para não expor a lista interna

    def depositar(self, valor):
        if valor <= 0:
            raise ValueError('Valor deve ser positivo')
        self._transacoes.append(('deposito', valor))

    def sacar(self, valor):
        if valor <= 0:
            raise ValueError('Valor deve ser positivo')
        if valor > self.saldo:
            raise ValueError('Saldo insuficiente')
        self._transacoes.append(('saque', valor))

# Testes
c = ContaBancaria()
print(f'Saldo inicial: {c.saldo}')
c.depositar(100)
print(f'Saldo após depósito: {c.saldo}')
print(f'Histórico: {c.historico}')
c.sacar(30)
print(f'Saldo após saque: {c.saldo}')
print(f'Histórico: {c.historico}')
try:
    c.sacar(200)
except ValueError as e:
    print(f'Erro: {e}')
try:
    c.depositar(-10)
except ValueError as e:
    print(f'Erro: {e}')

Passo a passo

  1. A classe inicia com uma lista vazia _transacoes que armazenará tuplas (tipo, valor).
  2. A property saldo percorre as transações, somando depósitos e subtraindo saques, e retorna o total. Como não há setter, é somente leitura.
  3. A property historico retorna uma cópia da lista (list(self._transacoes)) para que modificações externas não afetem o estado interno.
  4. O método depositar valida se o valor é positivo; se não, lança ValueError. Caso contrário, adiciona a tupla ('deposito', valor) ao histórico.
  5. O método sacar também valida o valor positivo. Depois, verifica se o valor é maior que o saldo atual (usando a property saldo); se for, lança ValueError('Saldo insuficiente'). Senão, adiciona ('saque', valor).
  6. Nos testes, criamos uma conta, fazemos um depósito de 100, imprimimos saldo e histórico. Em seguida, sacamos 30 e imprimimos novamente.
  7. Tentamos sacar 200 (mais que o saldo) e depositar -10, capturando e imprimindo as mensagens de erro.

Por que funciona

O saldo é calculado dinamicamente a partir do histórico, então nunca fica dessincronizado. As properties fornecem acesso controlado: saldo e historico são somente leitura, impedindo alterações diretas. Os métodos depositar e sacar são os únicos pontos de entrada para modificar o estado, garantindo validações consistentes.

Erros comuns

  • Retornar a lista interna diretamente: se historico retornar self._transacoes, o usuário pode modificar a lista e quebrar o saldo. Use list(...) para copiar.
  • Não validar o valor no saque: se permitir saques negativos, o saldo aumentaria indevidamente. Sempre valide valor > 0.
  • Calcular o saldo incorretamente: esquecer de subtrair saques ou somar depósitos. Revise a lógica de soma/subtração.
  • Permitir saque maior que o saldo: verifique valor > self.saldo antes de registrar a transação.

Outra forma de resolver

Poderia-se armazenar o saldo como atributo e atualizá-lo a cada operação, mas isso exigiria sincronização manual e poderia levar a inconsistências. A abordagem com histórico é mais robusta e demonstra o poder de atributos calculados.

Saída esperada

Saldo inicial: 0
Saldo após depósito: 100
Histórico: [('deposito', 100)]
Saldo após saque: 70
Histórico: [('deposito', 100), ('saque', 30)]
Erro: Saldo insuficiente
Erro: Valor deve ser positivo