Trilha de aprendizado · Nível 11 · Tutorial 4

Integrar coleções próprias à sintaxe de acesso

Ao concluir, você poderá oferecer tamanho, acesso por colchetes, pertencimento e alterações controladas em uma coleção própria, mantendo convenções familiares do Python.

  • Nível: Avançado
  • Duração: 22 min
  • 7 passos
Integrar coleções próprias à sintaxe de acesso

O que você vai percorrer

  1. Definir o contrato e oferecer tamanho Crie uma coleção de títulos com regras claras e integre seu tamanho a len() e à avaliação booleana do Python. 3 min
  2. Ler posições com __getitem__ Faça a coleção responder à leitura por colchetes com o mesmo comportamento de índices de uma lista. 3 min
  3. Distinguir índices de fatias Amplie o acesso por colchetes para que recortes retornem novas coleções independentes. 3 min
  4. Definir o significado de pertencimento Implemente pertencimento por valor para que a coleção responda de forma coerente a in e not in. 2 min
  5. Atualizar posições sem quebrar as regras Implemente substituições posicionais que preservam as regras da coleção e o estado quando uma operação falha. 4 min
  6. Remover posições e limitar a interface Implemente remoção posicional com del, preserve o contrato da coleção e avalie seus efeitos pela interface pública. 3 min
  7. Validar a coleção pela interface pública Execute um roteiro local completo para confirmar resultados, exceções e preservação de estado usando somente a sintaxe pública da coleção. 4 min

O que você vai aprender

  • Implementar len, acesso por colchetes e in para uma classe que encapsula uma sequência.
  • Distinguir o recebimento de um índice do recebimento de uma fatia em __getitem__.
  • Oferecer atualização e remoção somente quando forem compatíveis com as regras da coleção.
  • Verificar resultados e exceções pela sintaxe pública, sem depender de chamadas diretas aos métodos especiais.

Antes de começar

  • Definir representações com __repr__ e __str__
  • Organizar e atualizar dados em listas
  • Acessar e recortar textos com índices e fatias
  • Implementar iteráveis e iteradores em classes
  • Preservar regras de validade em objetos

Passo 1 de 7

Definir o contrato e oferecer tamanho

Crie uma coleção de títulos com regras claras e integre seu tamanho a len() e à avaliação booleana do Python.

Contrato da ColecaoDeTitulos

Uma sequência com regras próprias

Nosso exemplo contínuo será ColecaoDeTitulos. Ela guarda títulos como uma sequência de strings.

O contrato é simples:

  • cada título deve ser uma str não vazia e não formada apenas por espaços;
  • títulos repetidos são permitidos;
  • a coleção pode estar vazia.

A classe recebe uma lista, valida cada título e cria uma cópia. Assim, mudanças posteriores na lista usada na construção não alteram a coleção.

Estados permitidos

A imagem contrasta uma coleção vazia com outra que possui títulos, incluindo uma repetição válida.

Diagrama mostrando uma ColecaoDeTitulos vazia e outra contendo três cartões de título, dois deles iguais, ligados a uma lista interna independente.

Vazia e com títulos repetidos são estados válidos; títulos em branco não são.

Implementar len() pela lista interna

Da sintaxe pública ao método especial

Quando alguém escreve len(colecao), o Python procura __len__ no tipo da coleção. Esse método deve devolver um inteiro não negativo.

Não mantenha um contador separado: ele poderia se desencontrar dos dados. Como a lista interna já sabe quantos títulos guarda, delegue o cálculo a ela com len(self._titulos).

Classe de partida com __len__

Use esta implementação em um arquivo Python. __iter__ já permite percorrer a coleção; agora __len__ conecta o objeto a len().

python
class ColecaoDeTitulos:
    def __init__(self, titulos):
        self._titulos = []

        for titulo in titulos:
            self._validar_titulo(titulo)
            self._titulos.append(titulo)

    @staticmethod
    def _validar_titulo(titulo):
        if not isinstance(titulo, str):
            raise TypeError("cada título deve ser uma string")
        if not titulo.strip():
            raise ValueError("um título não pode ser vazio ou só espaços")

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

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


origem = ["Python Profundo", "Coleções", "Python Profundo"]
colecao = ColecaoDeTitulos(origem)
origem.append("Incluído só na lista externa")

print(len(colecao))  # 3
print(list(colecao))  # ['Python Profundo', 'Coleções', 'Python Profundo']

Tamanho também define a verdade

Sem __bool__, o Python usa __len__

Esta classe não define __bool__. Nesse caso, ao avaliar bool(colecao) — ou ao usar a coleção em um if — o Python usa seu tamanho:

  • tamanho 0 resulta em False;
  • tamanho positivo resulta em True.

Isso mantém uma convenção familiar: uma coleção vazia é falsa, e uma coleção com pelo menos um elemento é verdadeira.

Exemplo

Observando pela interface pública

vazia = ColecaoDeTitulos([])
com_titulo = ColecaoDeTitulos(["Modelo de Dados"])

print(len(vazia))        # 0
print(bool(vazia))       # False
print(bool(com_titulo))  # True

if com_titulo:
    print("Há títulos na coleção")

Use len() e bool(), não chamadas diretas como colecao.__len__(). A sintaxe pública é o contrato que os usuários da classe realmente utilizam.

Verifique os estados

Associe cada coleção ao resultado

Considere as coleções abaixo, todas construídas validamente. Relacione cada estado ao par (len(...), bool(...)) correspondente.

Toque em um item e depois no par correspondente.

Passo 2 de 7

Ler posições com __getitem__

Faça a coleção responder à leitura por colchetes com o mesmo comportamento de índices de uma lista.

Colchetes chamam o protocolo de acesso

Da sintaxe ao método

Depois de definir o tamanho, vamos permitir a leitura de um título individual. Quando você escreve colecao[indice], o Python encaminha indice para __getitem__.

Nesta etapa, cada acesso posicional deve retornar uma str. A lista interna já conhece índices positivos e negativos; portanto, delegar o acesso a ela mantém as convenções que o usuário espera.

Índices em duas direções

Diagrama de três títulos em uma lista interna: o primeiro pode ser acessado por índice zero e o último tanto por índice dois quanto por índice menos um.

Índices negativos contam a partir do fim: -1 seleciona o último título.

Implementação por delegação

Acrescente __getitem__ à classe

Use a lista copiada e validada na etapa anterior, armazenada em self._titulos.

python
class ColecaoDeTitulos:
    def __init__(self, titulos):
        self._titulos = []
        for titulo in titulos:
            if not isinstance(titulo, str):
                raise TypeError("cada título deve ser uma string")
            if not titulo.strip():
                raise ValueError("cada título deve ter conteúdo")
            self._titulos.append(titulo)

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

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

    def __getitem__(self, indice):
        return self._titulos[indice]

Exemplo

Leituras públicas

titulos = ColecaoDeTitulos(["Duna", "Ensaio sobre a Cegueira", "Kindred"])

titulos[0] resulta em "Duna".
titulos[-1] resulta em "Kindred".

Não converta indice com int(). A delegação deixa a lista decidir quais índices aceita e quais exceções deve produzir.

Erros também fazem parte do contrato

Preserve as exceções da lista

A delegação também preserva falhas úteis:

  • Uma posição inexistente produz IndexError, inclusive ao acessar uma coleção vazia.
  • Uma chave como "0" ou 1.0 produz TypeError.

Não capture nem troque essas exceções. Assim, a coleção se comporta como uma sequência no acesso posicional.

Preveja o acesso

Considere:

titulos = ColecaoDeTitulos(["Duna", "Kindred", "Solaris"])

Qual é o resultado de titulos[-1]?

Confira pela sintaxe do usuário

Teste localmente

Execute estes acessos depois de criar a classe. Observe que os testes usam colchetes, nunca uma chamada direta a __getitem__.

python
titulos = ColecaoDeTitulos(["Duna", "Kindred"])

assert titulos[0] == "Duna"
assert titulos[-1] == "Kindred"

try:
    titulos[2]
except IndexError:
    pass
else:
    raise AssertionError("era esperado IndexError")

try:
    titulos["0"]
except TypeError:
    pass
else:
    raise AssertionError("era esperado TypeError")

vazia = ColecaoDeTitulos([])
try:
    vazia[0]
except IndexError:
    pass
else:
    raise AssertionError("era esperado IndexError")

Identifique a exceção

Qual exceção é produzida por titulos["0"], considerando a implementação por delegação?

Passo 3 de 7

Distinguir índices de fatias

Amplie o acesso por colchetes para que recortes retornem novas coleções independentes.

Uma chave pode ser um índice ou uma fatia

O que chega a __getitem__

Até agora, em colecao[1], __getitem__ recebe o índice 1. Já em colecao[1:4:2], ele recebe um objeto slice.

Esse objeto guarda os componentes da fatia em start, stop e step. Componentes omitidos são representados por None: em colecao[:], a chave equivale a slice(None, None, None).

O contrato será diferente conforme a chave: um índice retorna uma str; uma fatia retorna uma nova ColecaoDeTitulos.

Índice e fatia seguem caminhos diferentes

Use o diagrama para relacionar a sintaxe pública ao valor recebido pelo método e ao tipo retornado.

Diagrama mostrando colecao[1] entregando o inteiro 1 e retornando um título, enquanto colecao[1:4:2] entrega um objeto slice com start 1, stop 4 e step 2 e retorna uma nova coleção com os títulos das posições 1 e 3.

A presença de dois-pontos nos colchetes cria um objeto slice; não uma sequência de índices.

Delegue o recorte à lista interna

Não interprete os limites manualmente

Teste primeiro se a chave é um slice. Nesse caso, deixe a lista interna aplicar a fatia: ela já trata limites além do tamanho, passos negativos e percursos invertidos. Em seguida, envolva a lista resultante em uma nova coleção.

Para qualquer outra chave, mantenha a delegação posicional já existente. Assim, índices inexistentes continuam gerando IndexError e tipos de índice inadequados continuam gerando TypeError.

__getitem__ com os dois retornos

Substitua o método anterior por esta versão na sua classe. O construtor já valida e copia a lista recebida.

python
def __getitem__(self, chave):
    if isinstance(chave, slice):
        titulos_recortados = self._titulos[chave]
        return ColecaoDeTitulos(titulos_recortados)

    return self._titulos[chave]

Exemplo

Resultados do contrato

Considerando titulos = ColecaoDeTitulos(["Duna", "Ensaio", "Fundação", "Solaris"]):

titulos[-1] resulta em "Solaris" — uma string.

titulos[1:10:2] resulta em uma nova ColecaoDeTitulos com ["Ensaio", "Solaris"]. O limite 10 não é erro: fatias podem ultrapassar o fim.

titulos[3:1] resulta em uma coleção vazia válida. Já titulos[10] gera IndexError, e titulos[::0] gera ValueError.

Dica

Independência do recorte

Como a fatia da lista produz outra lista e o construtor copia a entrada, o recorte não é uma visão da coleção original. Alterações permitidas posteriormente em uma das coleções não devem alterar a outra.

Confira a chave e o retorno

Complete a condição

Para separar o caminho de uma fatia do caminho de um índice, complete:

if isinstance(chave, _____):

Preveja o retorno

Se titulos tem quatro elementos, qual é o resultado esperado de titulos[10:20]?

Passo 4 de 7

Definir o significado de pertencimento

Implemente pertencimento por valor para que a coleção responda de forma coerente a in e not in.

Pertencimento é uma pergunta sobre valor

O que `in` consulta

Em valor in colecao, o Python usa __contains__ para perguntar se aquele valor pertence à coleção.

Para ColecaoDeTitulos, o contrato é direto: um título pertence quando existe um elemento inteiro e igual ao valor consultado. Não é uma busca por posição e nem por trecho de texto.

Igualdade completa, não trecho

Compare a consulta com cada título armazenado como um elemento independente.

Diagrama de uma coleção com os títulos Python Fluente e Código Limpo. A consulta Python Fluente é aceita por igualdade completa; a consulta Python é rejeitada por ser apenas uma substring.

"Python Fluente" pode pertencer; "Python" não pertence apenas por aparecer dentro de outro título.

Exemplo

Resultados esperados

colecao = ColecaoDeTitulos(["Python Fluente", "Código Limpo"])

"Python Fluente" in colecao  # True
"Python" in colecao          # False
0 in colecao                  # False
"Código Limpo" not in colecao  # False

Implementar `__contains__`

Delegue a busca

A lista interna já compara seus elementos por igualdade e devolve um booleano em uma consulta com in. Portanto, __contains__ só precisa delegar essa operação.

Assim, os valores reconhecidos por in são os mesmos títulos que podem ser lidos por índice e percorridos com for.

Método da coleção

Adicione este método à classe, ao lado de __len__, __iter__ e __getitem__:

python
def __contains__(self, valor):
    return valor in self._titulos

Casos que confirmam o contrato

Vazio, ausente e repetido

Uma coleção vazia não contém valor algum. Um título ausente também produz False. Se um título estiver repetido, basta haver uma ocorrência para que in produza True; pertencimento não informa quantas ocorrências existem.

Use a sintaxe pública

Execute no seu computador depois de incluir __contains__:

python
vazia = ColecaoDeTitulos([])
print("Python Fluente" in vazia)  # False

colecao = ColecaoDeTitulos(["Duna", "Duna", "Fundação"])
print("Duna" in colecao)          # True
print("Neuromancer" in colecao)   # False
print("Fundação" not in colecao)  # False

Verifique a interpretação

Título completo ou substring?

Se a coleção contém o título "Arquitetura Limpa", então a expressão "Limpa" in colecao deve resultar em True.

Passo 5 de 7

Atualizar posições sem quebrar as regras

Implemente substituições posicionais que preservam as regras da coleção e o estado quando uma operação falha.

Uma atribuição, duas responsabilidades

Substituir não é inserir

Na expressão colecao[indice] = valor, o Python chama __setitem__(indice, valor). Nesta coleção, a operação só pode substituir um título em uma posição que já existe — inclusive com índice negativo. Portanto, o tamanho não muda.

A operação precisa seguir este contrato: rejeitar fatias, validar o novo título e só então gravá-lo. Se algo for inválido, o conteúdo e o tamanho devem permanecer como estavam.

Fluxo de uma atualização segura

Diagrama mostrando uma chave de posição e um novo cartão de título passando por verificações antes de substituir um elemento de uma lista; tentativas com cartão em branco ou seleção de faixa são bloqueadas antes de alcançar a lista.

A validação vem antes da gravação: uma falha não pode deixar uma alteração parcial.

Dica

Delegue o que já é convenção da lista

Depois de validar o valor e rejeitar uma fatia, delegue self._titulos[indice] = valor. Assim, a lista preserva IndexError para posição inexistente e TypeError para um tipo de índice inadequado, sem conversões implícitas.

Implementação de __setitem__

Coleção com substituição posicional

Use esta versão completa para praticar localmente. Os métodos de leitura e pertencimento permanecem coerentes após uma substituição.

python
class ColecaoDeTitulos:
    def __init__(self, titulos):
        self._titulos = []
        for titulo in titulos:
            self._validar_titulo(titulo)
            self._titulos.append(titulo)

    @staticmethod
    def _validar_titulo(titulo):
        if not isinstance(titulo, str):
            raise TypeError("título deve ser uma string")
        if not titulo.strip():
            raise ValueError("título não pode ser vazio ou só espaços")

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

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

    def __getitem__(self, chave):
        resultado = self._titulos[chave]
        if isinstance(chave, slice):
            return ColecaoDeTitulos(resultado)
        return resultado

    def __contains__(self, titulo):
        return titulo in self._titulos

    def __setitem__(self, indice, titulo):
        if isinstance(indice, slice):
            raise TypeError("esta coleção não aceita atribuição por fatia")

        self._validar_titulo(titulo)
        self._titulos[indice] = titulo


colecao = ColecaoDeTitulos(["Duna", "Solaris", "Duna"])
colecao[-1] = "Fundação"

print(colecao[-1])       # Fundação
print("Fundação" in colecao)  # True
print(len(colecao))      # 3

A ordem evita alterações parciais

Ordene a atualização segura

Coloque as etapas de colecao[indice] = titulo na ordem adequada.

  1. Validar que o novo título é uma string não vazia nem composta só de espaços.
  2. Atribuir o título na lista interna pela posição recebida.
  3. Receber o índice e o novo título em __setitem__.
  4. Rejeitar o índice se ele for um objeto slice.

Pratique pela sintaxe pública

Teste no seu computador

Após executar o código anterior, rode o roteiro abaixo. Ele usa somente a interface pública. Observe que uma tentativa inválida deixa a coleção exatamente igual ao estado anterior.

Roteiro de verificação local

python
antes = list(colecao)
tamanho_antes = len(colecao)

try:
    colecao[0] = "   "
except ValueError as erro:
    print(type(erro).__name__)  # ValueError

print(list(colecao) == antes)       # True
print(len(colecao) == tamanho_antes)  # True

try:
    colecao[0:1] = "Contato"
except TypeError as erro:
    print(type(erro).__name__)  # TypeError

colecao[0] = "Contato"
print(colecao[0])          # Contato
print("Contato" in colecao)  # True
print(len(colecao))        # 3

Relate o que você observou

No seu roteiro local, relate uma substituição válida e uma operação rejeitada. Para cada caso, diga o que aconteceu com o conteúdo e com o tamanho da coleção.

Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 7

Remover posições e limitar a interface

Implemente remoção posicional com del, preserve o contrato da coleção e avalie seus efeitos pela interface pública.

`del` chama a remoção posicional

Uma operação pública, um método especial

A expressão del colecao[indice] faz o Python encaminhar o índice para __delitem__. Para manter o comportamento de posições — inclusive índices negativos e erros de índice inválido — delegue a remoção à lista interna.

O contrato desta coleção permite remover uma posição por vez. Portanto, uma chave slice é rejeitada explicitamente antes de qualquer mudança.

Efeito da remoção

Ao remover a posição 1, apenas aquele elemento sai; os elementos posteriores passam a ocupar uma posição anterior.

Diagrama de uma sequência de quatro títulos em que o segundo é removido e os dois seguintes se deslocam uma posição para a esquerda.

A remoção reduz o tamanho e desloca as posições seguintes; ela não remove todas as ocorrências de um mesmo título.

Implementação com contrato restrito

Adicione `__delitem__` à classe

Use a mesma lista interna _titulos já utilizada pela coleção.

python
def __delitem__(self, indice):
    if isinstance(indice, slice):
        raise TypeError("remoção por fatia não é permitida")

    del self._titulos[indice]

O que essa implementação preserva

Para um inteiro válido, a lista remove a posição. Um índice negativo continua funcionando. Uma posição inexistente produz IndexError, e um tipo de índice que a lista não aceita produz TypeError.

A verificação da fatia vem antes do del: assim, del colecao[1:3] sempre falha com TypeError e não altera a coleção. Remover o único elemento é válido: depois disso, len(colecao) é 0 e bool(colecao) é False.

Dica

Interface é uma escolha de contrato

Aceitar leitura por fatia não obriga a aceitar remoção por fatia. Do mesmo modo, uma coleção somente de leitura pode simplesmente não definir __setitem__ nem __delitem__. Ofereça apenas operações que preservem as regras do objeto.

Observe remoção, repetições e coleção vazia

Exemplo

Remover uma posição não remove todos os valores iguais

colecao = ColecaoDeTitulos(["Duna", "Fundação", "Duna"])

del colecao[0]

print(len(colecao))       # 2
print(colecao[0])         # Fundação
print("Duna" in colecao) # True: a outra ocorrência permaneceu

ultimo = ColecaoDeTitulos(["Solaris"])
del ultimo[0]

print(len(ultimo))  # 0
print(bool(ultimo)) # False

A remoção atua sobre a posição escolhida. Se há títulos repetidos, somente a ocorrência naquela posição é removida.

Atenção

Verifique pela sintaxe pública

Teste del colecao[indice], len(colecao), colecao[posicao], valor in colecao e bool(colecao). Não chame __delitem__ diretamente nem consulte _titulos: o contrato é o comportamento observado por quem usa a coleção.

Pratique: qual será o estado observável?

Remoção com índice negativo

Considere uma coleção que já implementa __delitem__ como no contrato:

colecao = ColecaoDeTitulos(["Duna", "Solaris", "Duna"])
del colecao[-1]

Qual afirmação é verdadeira depois da remoção?

Limite deliberado da interface

O que deve ocorrer em del colecao[1:3] nesta versão de ColecaoDeTitulos?

Passo 7 de 7

Validar a coleção pela interface pública

Execute um roteiro local completo para confirmar resultados, exceções e preservação de estado usando somente a sintaxe pública da coleção.

1. Implemente a coleção completa

Ponto de partida autocontido

No seu computador, crie um arquivo chamado colecao_titulos.py e copie a implementação abaixo. O contrato aceita apenas strings não vazias nem formadas só por espaços; repetições e uma coleção vazia são válidas.

A validação final deve observar apenas a interface pública: len, bool, colchetes, in, atribuição por colchetes e del.

colecao_titulos.py

python
class ColecaoDeTitulos:
    def __init__(self, titulos):
        titulos = list(titulos)
        for titulo in titulos:
            self._validar_titulo(titulo)
        self._titulos = titulos.copy()

    @staticmethod
    def _validar_titulo(titulo):
        if not isinstance(titulo, str):
            raise TypeError("cada título deve ser uma string")
        if not titulo.strip():
            raise ValueError("cada título deve ter conteúdo")

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

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

    def __getitem__(self, chave):
        if isinstance(chave, slice):
            return ColecaoDeTitulos(self._titulos[chave])
        return self._titulos[chave]

    def __contains__(self, valor):
        return valor in self._titulos

    def __setitem__(self, chave, valor):
        if isinstance(chave, slice):
            raise TypeError("a coleção não aceita atribuição por fatia")
        self._validar_titulo(valor)
        self._titulos[chave] = valor

    def __delitem__(self, chave):
        if isinstance(chave, slice):
            raise TypeError("a coleção não aceita remoção por fatia")
        del self._titulos[chave]

2. Execute as verificações públicas

Roteiro de validação

No fim do mesmo arquivo, acrescente o roteiro a seguir e execute python colecao_titulos.py no terminal. Se nada for exibido além da mensagem final, todos os assert passaram.

A função auxiliar verifica a exceção esperada, mas cada operação testada usa a sintaxe pública — nunca uma chamada direta como colecao.__getitem__(...).

Testes no mesmo arquivo

python
def esperar_erro(tipo_esperado, operacao):
    try:
        operacao()
    except tipo_esperado:
        return
    except Exception as erro:
        raise AssertionError(
            f"Esperava {tipo_esperado.__name__}, recebeu {type(erro).__name__}"
        ) from erro
    raise AssertionError(f"Esperava {tipo_esperado.__name__}, mas não houve erro")


# Coleção vazia: tamanho, valor booleano e acesso inexistente.
vazia = ColecaoDeTitulos([])
assert len(vazia) == 0
assert not vazia
assert "Python" not in vazia
esperar_erro(IndexError, lambda: vazia[0])

# Leitura por índice, pertencimento e tipos de índice inadequados.
titulos = ColecaoDeTitulos(["Python", "Duplicado", "Dados", "Duplicado"])
assert len(titulos) == 4
assert bool(titulos)
assert titulos[0] == "Python"
assert titulos[-1] == "Duplicado"
assert "Dados" in titulos
assert "Dad" not in titulos  # não é busca por substring
esperar_erro(IndexError, lambda: titulos[10])
esperar_erro(TypeError, lambda: titulos["0"])
esperar_erro(TypeError, lambda: titulos[1.0])

# Fatias retornam outra coleção; limites e percursos são delegados à lista.
recorte = titulos[1:3]
assert isinstance(recorte, ColecaoDeTitulos)
assert list(recorte) == ["Duplicado", "Dados"]
assert len(titulos[20:30]) == 0
assert len(titulos[3:1]) == 0
assert list(titulos[::-1]) == ["Duplicado", "Dados", "Duplicado", "Python"]
esperar_erro(ValueError, lambda: titulos[::0])

# O recorte é independente: alterá-lo não muda a coleção de origem.
recorte[0] = "Recorte alterado"
assert recorte[0] == "Recorte alterado"
assert titulos[1] == "Duplicado"

# Substituição válida conserva o tamanho e atualiza leitura e pertencimento.
titulos[2] = "Algoritmos"
assert len(titulos) == 4
assert titulos[2] == "Algoritmos"
assert "Dados" not in titulos
assert "Algoritmos" in titulos

# Operações rejeitadas não deixam alteração parcial observável.
antes = list(titulos)
esperar_erro(ValueError, lambda: titulos.__setitem__(0, "   "))
assert list(titulos) == antes
esperar_erro(TypeError, lambda: titulos.__setitem__(0, 42))
assert list(titulos) == antes
esperar_erro(TypeError, lambda: titulos.__setitem__(slice(0, 1), ["Novo"]))
assert list(titulos) == antes

# Remover uma posição desloca as seguintes e remove apenas aquela ocorrência.
del titulos[1]
assert list(titulos) == ["Python", "Algoritmos", "Duplicado"]
assert "Duplicado" in titulos
del titulos[-1]
assert "Duplicado" not in titulos
assert len(titulos) == 2

antes = list(titulos)
esperar_erro(TypeError, lambda: titulos.__delitem__(slice(0, 1)))
assert list(titulos) == antes
esperar_erro(IndexError, lambda: titulos[20])

print("Verificações concluídas com sucesso.")

3. Corrija o roteiro para usar somente a interface

Atenção

Ajuste necessário no roteiro

O roteiro acima ainda contém chamadas diretas a __setitem__ e __delitem__ dentro de algumas verificações de erro. Isso contradiz o objetivo da validação pública. Antes de executar, substitua cada uma delas por uma função local que use a sintaxe pública.

Exemplo: para testar uma atribuição inválida, defina uma função que execute titulos[0] = " " e passe essa função a esperar_erro. Para uma fatia, use titulos[0:1] = ["Novo"]. Faça o equivalente para del titulos[0:1].

Relate suas evidências

Depois de adaptar as verificações rejeitadas para usar somente a sintaxe pública e executar o arquivo, o que ocorreu? Relate duas evidências concretas observadas.

Escreva pelo menos 120 caracteres (0/120).

4. Fechamento: contrato observável

Resumo

O que foi validado

  • len(colecao) e bool(colecao) refletem o número atual de títulos.
  • Um índice lê uma string; uma fatia lê uma nova ColecaoDeTitulos, inclusive quando o recorte é vazio ou invertido.
  • Índices inválidos preservam as exceções da lista; um passo zero em fatia gera ValueError.
  • in consulta títulos completos por valor, e não posições nem substrings.
  • Atribuição e remoção aceitam apenas posições, validam ou delegam a operação e não devem deixar estado parcialmente alterado quando falham.
  • Testes de integração devem usar a sintaxe que usuários da classe realmente usam, sem chamar métodos especiais diretamente nem inspecionar armazenamento interno.

Tutorial concluído

Parabéns! Você concluiu: Integrar coleções próprias à sintaxe de acesso

Você validou uma coleção própria pela interface pública. Agora consegue conectar operações familiares do Python a um contrato explícito de leitura, pertencimento e alterações controladas.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso