Desafio Python

Lista Personalizada com Operadores

Enunciado

Crie uma classe MinhaLista que encapsula uma lista de números. Implemente __len__ para retornar o tamanho, __iter__ para permitir iteração, __add__ para concatenar duas listas (retornando uma nova MinhaLista), __getitem__ para acessar elementos por índice e __repr__ para representação. Teste com operações de len(), iteração, concatenação e acesso por índice.

Requisitos

  • A classe deve ter um construtor que aceita um iterável e armazena uma lista interna.
  • __len__ retorna o número de elementos.
  • __iter__ retorna um iterador sobre os elementos.
  • __add__ aceita outra MinhaLista e retorna uma nova MinhaLista com os elementos concatenados.
  • __getitem__ permite acessar elementos por índice inteiro.
  • __repr__ retorna uma string no formato MinhaLista([1, 2, 3]).

Código inicial

class MinhaLista:
    def __init__(self, iteravel):
        self._dados = list(iteravel)

    # Implemente os métodos especiais

# Teste
ml1 = MinhaLista([1, 2, 3])
ml2 = MinhaLista([4, 5])
print(len(ml1))          # 3
print(list(ml1))         # [1, 2, 3]
print(ml1[1])            # 2
ml3 = ml1 + ml2
print(ml3)               # MinhaLista([1, 2, 3, 4, 5])

Saída esperada

3
[1, 2, 3]
2
MinhaLista([1, 2, 3, 4, 5])
Ver dica

Para __add__, verifique se o outro operando é MinhaLista e retorne NotImplemented caso contrário. __getitem__ pode delegar para a lista interna.

Mostrar solução
class MinhaLista:
    def __init__(self, iteravel):
        self._dados = list(iteravel)

    def __len__(self):
        return len(self._dados)

    def __iter__(self):
        return iter(self._dados)

    def __add__(self, other):
        if not isinstance(other, MinhaLista):
            return NotImplemented
        return MinhaLista(self._dados + other._dados)

    def __getitem__(self, index):
        return self._dados[index]

    def __repr__(self):
        return f"MinhaLista({self._dados!r})"

ml1 = MinhaLista([1, 2, 3])
ml2 = MinhaLista([4, 5])
print(len(ml1))
print(list(ml1))
print(ml1[1])
ml3 = ml1 + ml2
print(ml3)

Passo a passo

  1. __init__ converte o iterável recebido em uma lista e armazena em _dados.
  2. __len__ delega para len(self._dados).
  3. __iter__ retorna um iterador sobre _dados usando iter().
  4. __add__ verifica se other é MinhaLista; se não, retorna NotImplemented. Caso contrário, cria uma nova MinhaLista com a concatenação das listas internas.
  5. __getitem__ permite acesso indexado delegando para _dados[index].
  6. __repr__ usa !r para representar a lista interna de forma não ambígua.
  7. Testamos com len, iteração, acesso por índice e concatenação.

Por que funciona

Ao implementar __len__, len(ml1) funciona. __iter__ permite que list(ml1) e loops for percorram os elementos. __add__ possibilita o uso do operador +. __getitem__ habilita ml1[1]. __repr__ fornece uma representação legível. Esses métodos fazem com que MinhaLista se comporte como uma sequência nativa.

Erros comuns

  • Esquecer de retornar NotImplemented em __add__ para tipos não suportados: se tentar ml1 + 5, o Python lançará TypeError em vez de tentar a operação reversa.
  • Implementar __iter__ retornando self._dados em vez de iter(self._dados): isso retornaria a lista, que é iterável, mas não é um iterador; no entanto, funciona em for, mas não em next(). O correto é retornar um iterador.
  • Não implementar __getitem__ e tentar indexar: TypeError: 'MinhaLista' object is not subscriptable.
  • Usar __repr__ sem !r: se os elementos forem strings, a representação pode ficar ambígua.

Outra forma de resolver

Poderíamos herdar de list e sobrescrever apenas __add__ e __repr__. Exemplo:

class MinhaLista(list):
    def __add__(self, other):
        if not isinstance(other, MinhaLista):
            return NotImplemented
        return MinhaLista(super().__add__(other))
    def __repr__(self):
        return f"MinhaLista({super().__repr__()})"

Isso é preferível quando queremos todas as funcionalidades de lista sem reimplementar tudo.

Saída esperada

3
[1, 2, 3]
2
MinhaLista([1, 2, 3, 4, 5])