
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.
Trilha de aprendizado · Nível 11 · Tutorial 4
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.
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
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
Distinguir índices de fatias
Amplie o acesso por colchetes para que recortes retornem novas coleções independentes. 3 min
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
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
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
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

Passo 1 de 7
Crie uma coleção de títulos com regras claras e integre seu tamanho a len() e à avaliação booleana do Python.
Nosso exemplo contínuo será ColecaoDeTitulos. Ela guarda títulos como uma sequência de strings.
O contrato é simples:
str não vazia e não formada apenas por espaços;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.
A imagem contrasta uma coleção vazia com outra que possui títulos, incluindo uma repetição válida.

Vazia e com títulos repetidos são estados válidos; títulos em branco não são.
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).
Use esta implementação em um arquivo Python. __iter__ já permite percorrer a coleção; agora __len__ conecta o objeto a len().
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']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:
0 resulta em False;True.Isso mantém uma convenção familiar: uma coleção vazia é falsa, e uma coleção com pelo menos um elemento é verdadeira.
Exemplo
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.
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
Faça a coleção responder à leitura por colchetes com o mesmo comportamento de índices de uma lista.
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 negativos contam a partir do fim: -1 seleciona o último título.
Use a lista copiada e validada na etapa anterior, armazenada em self._titulos.
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
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.
A delegação também preserva falhas úteis:
IndexError, inclusive ao acessar uma coleção vazia."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.
Considere:
titulos = ColecaoDeTitulos(["Duna", "Kindred", "Solaris"])
Qual é o resultado de titulos[-1]?
Execute estes acessos depois de criar a classe. Observe que os testes usam colchetes, nunca uma chamada direta a __getitem__.
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")Qual exceção é produzida por titulos["0"], considerando a implementação por delegação?

Passo 3 de 7
Amplie o acesso por colchetes para que recortes retornem novas coleções independentes.
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.
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.](https://cursa-json-routes.s3.amazonaws.com/learning-paths/pt/python/b730454e0225/level-11/tutorial-04/step-03/screen-01/item-02.webp)
A presença de dois-pontos nos colchetes cria um objeto slice; não uma sequência de índices.
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.
Substitua o método anterior por esta versão na sua classe. O construtor já valida e copia a lista recebida.
def __getitem__(self, chave):
if isinstance(chave, slice):
titulos_recortados = self._titulos[chave]
return ColecaoDeTitulos(titulos_recortados)
return self._titulos[chave]Exemplo
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
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.
Para separar o caminho de uma fatia do caminho de um índice, complete:
if isinstance(chave, _____):
Se titulos tem quatro elementos, qual é o resultado esperado de titulos[10:20]?

Passo 4 de 7
Implemente pertencimento por valor para que a coleção responda de forma coerente a in e not in.
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.
Compare a consulta com cada título armazenado como um elemento independente.

"Python Fluente" pode pertencer; "Python" não pertence apenas por aparecer dentro de outro título.
Exemplo
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 # FalseA 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.
Adicione este método à classe, ao lado de __len__, __iter__ e __getitem__:
def __contains__(self, valor):
return valor in self._titulosUma 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.
Execute no seu computador depois de incluir __contains__:
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) # FalseSe 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
Implemente substituições posicionais que preservam as regras da coleção e o estado quando uma operação falha.
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.

A validação vem antes da gravação: uma falha não pode deixar uma alteração parcial.
Dica
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.
Use esta versão completa para praticar localmente. Os métodos de leitura e pertencimento permanecem coerentes após uma substituição.
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)) # 3Coloque as etapas de colecao[indice] = titulo na ordem adequada.
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.
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)) # 3No 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
Implemente remoção posicional com del, preserve o contrato da coleção e avalie seus efeitos pela interface pública.
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.
Ao remover a posição 1, apenas aquele elemento sai; os elementos posteriores passam a ocupar uma posição anterior.

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.
Use a mesma lista interna _titulos já utilizada pela coleção.
def __delitem__(self, indice):
if isinstance(indice, slice):
raise TypeError("remoção por fatia não é permitida")
del self._titulos[indice]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
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.
Exemplo
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)) # FalseA remoção atua sobre a posição escolhida. Se há títulos repetidos, somente a ocorrência naquela posição é removida.
Atenção
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.
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?
O que deve ocorrer em del colecao[1:3] nesta versão de ColecaoDeTitulos?

Passo 7 de 7
Execute um roteiro local completo para confirmar resultados, exceções e preservação de estado usando somente a sintaxe pública da coleção.
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.
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]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__(...).
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.")Atenção
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].
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).
Resumo
len(colecao) e bool(colecao) refletem o número atual de títulos.ColecaoDeTitulos, inclusive quando o recorte é vazio ou invertido.ValueError.in consulta títulos completos por valor, e não posições nem substrings.Parabéns! Você concluiu: Integrar coleções próprias à sintaxe de acesso
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks