Desafio Python

Carrinho de compras com itens e total

Enunciado

Implemente duas classes: Produto (com nome e preco) e Carrinho (com uma lista de itens). O carrinho deve ter adicionar(produto, quantidade=1), remover(nome) e total() que soma preco * quantidade de cada item. Guarde os itens como uma lista de tuplas (produto, quantidade). Imprima o total formatado com duas casas decimais.

Requisitos

  • Produto tem atributos de instância nome e preco.
  • Carrinho.adicionar aceita um Produto e uma quantidade (padrão 1).
  • Carrinho.remover(nome) remove o item cujo produto tem aquele nome; se não existir, levanta ValueError.
  • Carrinho.total() retorna a soma de preco * quantidade de todos os itens.

Código inicial

class Produto:
    def __init__(self, nome, preco):
        pass


class Carrinho:
    def __init__(self):
        pass

    def adicionar(self, produto, quantidade=1):
        pass

    def remover(self, nome):
        pass

    def total(self):
        pass


carrinho = Carrinho()
carrinho.adicionar(Produto("Caneta", 2.50), 4)
carrinho.adicionar(Produto("Caderno", 15.00))
carrinho.adicionar(Produto("Borracha", 1.20), 2)
carrinho.remover("Borracha")
print(f"Total: R$ {carrinho.total():.2f}")

Saída esperada

Total: R$ 25.00
Ver dica

Guarde os itens como (produto, quantidade) em self.itens. Em remover, itere sobre a lista e use enumerate para achar o índice; del self.itens[i] remove.

Mostrar solução
class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco


class Carrinho:
    def __init__(self):
        # lista de tuplas (produto, quantidade)
        self.itens = []

    def adicionar(self, produto, quantidade=1):
        if quantidade <= 0:
            raise ValueError("quantidade deve ser positiva")
        # se o produto já existe, soma a quantidade
        for i, (p, q) in enumerate(self.itens):
            if p.nome == produto.nome:
                self.itens[i] = (p, q + quantidade)
                return
        self.itens.append((produto, quantidade))

    def remover(self, nome):
        for i, (p, _) in enumerate(self.itens):
            if p.nome == nome:
                del self.itens[i]
                return
        raise ValueError(f"produto não encontrado: {nome}")

    def total(self):
        return sum(p.preco * q for p, q in self.itens)


carrinho = Carrinho()
carrinho.adicionar(Produto("Caneta", 2.50), 4)
carrinho.adicionar(Produto("Caderno", 15.00))
carrinho.adicionar(Produto("Borracha", 1.20), 2)
carrinho.remover("Borracha")
print(f"Total: R$ {carrinho.total():.2f}")

Passo a passo

  1. Produto guarda nome e preco como atributos de instância no __init__.
  2. Carrinho.__init__ cria self.itens = [], uma lista vazia exclusiva de cada carrinho.
  3. Em adicionar, validamos quantidade > 0 para evitar entradas inválidas.
  4. Percorremos self.itens com enumerate; se o produto já existe (mesmo nome), somamos a quantidade e retornamos.
  5. Se não encontramos, adicionamos a tupla (produto, quantidade) ao final da lista.
  6. remover procura pelo nome; ao achar, usa del self.itens[i] e sai; se não achar, levanta ValueError.
  7. total usa uma generator expression dentro de sum para multiplicar preco * quantidade de cada item.
  8. O print formata o resultado com :.2f, exibindo duas casas decimais.

Por que funciona

Cada Carrinho tem sua própria lista itens, porque ela é criada em __init__ e acessada via self. Isso é o oposto de colocar itens = [] no corpo da classe, que criaria uma lista compartilhada entre todos os carrinhos — bug clássico. Os métodos operam sobre self.itens, então cada objeto mantém seu estado isolado. Produto e Carrinho colaboram por composição: o carrinho contém produtos.

Erros comuns

  • Definir itens = [] no corpo da classe: todos os carrinhos compartilham a mesma lista e itens vazam entre eles. Corrija com self.itens = [] no __init__.
  • Em remover, esquecer o return após del pode causar IndexError numa iteração seguinte. Use return imediatamente.
  • Comparar produtos com == sem definir __eq__ faz o Python comparar identidades; por isso comparamos por p.nome aqui.
  • Esquecer raise ValueError quando o produto não existe faz a função retornar None silenciosamente, escondendo erros.

Outra forma de resolver

Use um dicionário mapeando nome para (produto, quantidade), o que torna busca e remoção O(1):

self.itens = {}  # nome -> (produto, quantidade)

Preferível quando o carrinho é grande e a busca por nome é frequente; a lista é mais simples e preserva a ordem de inserção.

Saída esperada

Total: R$ 25.00