Skip to content
This page has been auto-translated and may contain errors.View in English

Tuplas e conjuntos

docs.scrimba.com

Você conhece listas. Python tem mais dois tipos de coleção que resolvem problemas que listas não conseguem. Tuplas guardam um grupo fixo de valores que nunca vai mudar. Conjuntos guardam apenas valores únicos e deixam você verificar pertencimento instantaneamente, não importa o tamanho da coleção.

O kit de coleções do Python tem quatro tipos. Listas e dicionários lidam com a maioria dos casos gerais. Tuplas e conjuntos resolvem os casos específicos: registros fixos onde a imutabilidade é um ativo, e coleções de valores únicos onde testes de pertencimento O(1) são a prioridade.

Além de list e dict, Python oferece tuple (uma sequência de comprimento fixo que você não consegue mudar após criar) e set (uma coleção desordenada de valores únicos, construída em uma tabela de dispersão, a mesma estrutura de busca por conteúdo que torna dict rápido). A divisão que leva a cada decisão abaixo: uma tupla é hashable (seu conteúdo pode ser reduzido a um único número para atuar como chave de dicionário ou membro de conjunto), uma lista não é. Escolha baseado no que você precisa a coleção fazer, não em qual você tira por hábito.

Tuplas

Uma tupla é um grupo ordenado de valores que não pode ser mudado após criar. Parênteses definem uma tupla, mas são opcionais. A vírgula é o que realmente faz uma tupla. Uma tupla de um único item requer uma vírgula no final.

Tuplas são sequências imutáveis. A vírgula, não os parênteses, é o que cria uma tupla. A imutabilidade as torna hashable quando todos seus elementos também são, o que abre casos de uso que listas não conseguem preencher: chaves de dicionário, membros de conjunto e registros de estrutura fixa.

Uma tuple é uma sequência de comprimento fixo que você não consegue mudar uma vez que ela existe. Porque nada dentro dela se move, Python consegue calcular um único hash para ela (um número derivado de seu conteúdo) enquanto cada elemento for ele próprio hashable, o que permite que uma tupla sirva como chave de dicionário ou membro de conjunto onde uma lista não consegue. Indexação e fatiamento funcionam exatamente como em uma lista; não há atribuição de item, então point[0] = 99 levanta TypeError quando essa linha executa. A forma de um único item (42,) precisa da vírgula no final. Sem ela, os parênteses só agrupam e você recebe o valor nu 42.

python
point = (10, 20)
rgb = (255, 128, 0)
dimensions = (1920, 1080)
single = (42,)            # vírgula no final obrigatória para tupla de um item
also_tuple = 42, 99       # parênteses são opcionais; a vírgula faz uma tupla

Acesso por índice funciona exatamente como uma lista. Tentar mudar um item levanta TypeError:

Indexação, fatiamento e índices negativos funcionam identicamente às listas. Qualquer tentativa de atribuição por índice levanta TypeError; isso é intencional, não uma limitação.

Ler de uma tupla, por índice, índice negativo ou fatia, se comporta exatamente como uma lista. Escrever é o que diferencia: não há atribuição de item, então point[0] = 99 levanta TypeError no momento em que essa linha executa. Esse é o ponto todo de escolher uma tupla em vez de uma lista. É um valor que você e cada leitor depois pode confiar que não vai mudar por baixo.

python
point = (10, 20)
point[0]    # 10
point[1]    # 20
point[-1]   # 20

point[0] = 99    # TypeError: 'tuple' object does not support item assignment
JunoTuplas Uma tupla é um grupo ordenado de valores que você não consegue mudar depois de criar. A vírgula é o que a cria, não os parênteses, é por isso que (42,) precisa daquela vírgula solitária no final. Tente reatribuir um item e você recebe um TypeError, e esse sentimento de bloqueado é exatamente por que você escolheria uma tupla.
JunoTuplas Tuplas são sequências imutáveis, e a vírgula as faz, não os parênteses. Essa imutabilidade é uma funcionalidade: a torna hashable quando seu conteúdo é, então pode ser uma chave de dicionário ou membro de conjunto onde uma lista não consegue. Atribuição de item levanta TypeError por design.
JunoTuplas Ler uma tupla combina com uma lista; escrever é o que falta, então point[0] = 99 levanta TypeError em tempo de execução. Conte com isso: uma tupla é o valor que você passa quando precisa garantir que nada a jusante vai reescrever. A forma de um item (42,) pega todo mundo uma vez.

Quando usar uma tupla

Use uma tupla quando você tem um pequeno grupo de valores relacionados que pertencem juntos e não vão mudar. Coordenadas (x, y), uma cor (r, g, b), um par nome-pontuação ("Maria", 87). A estrutura fixa sinaliza a qualquer um lendo o código que este grupo é tratado como uma unidade única.

Tuplas comunicam estrutura fixa: um grupo de valores onde posição carrega significado e o grupo é tratado como uma unidade. Sua hashability as torna válidas como chaves de dicionário, o que listas não conseguem ser. O contrato que uma tupla sinaliza é: esses valores pertencem juntos e não devem mudar.

Recorra a uma tupla quando você tem um registro de forma fixa: um número conhecido de campos onde cada posição significa algo específico. Porque uma tupla é hashable (seu conteúdo se reduz a um único número), ela cabe em qualquer lugar onde um valor hashable é necessário: uma chave de dicionário, um membro de conjunto ou os argumentos em cache atrás de functools.lru_cache, que se lembra dos resultados de uma função com chave nos argumentos que você a chamou. O sinal semântico também difere de uma lista. Uma tupla diz "esses campos pertencem juntos e cada posição tem um significado" (uma coordenada, uma cor), enquanto uma lista diz "uma sequência de coisas similares cujo comprimento pode mudar."

JunoQuando usar uma tupla Recorra a uma tupla quando você tem um pequeno grupo fixo onde cada spot significa algo: uma coordenada, uma cor (r, g, b), um par nome-e-pontuação. Porque tuplas são hashable, você pode até usar uma como chave de dicionário. Uma lista não consegue fazer isso, o que pega as pessoas da primeira vez que tentam.
JunoQuando usar uma tupla Uma tupla sinaliza estrutura fixa: posição carrega significado e o grupo viaja como uma unidade. Isso é a diferença de uma lista, que diz "uma sequência cujo comprimento pode variar." E como tuplas são hashable, funcionam como chaves de dicionário onde listas levantam TypeError.
JunoQuando usar uma tupla Tupla para um registro de forma fixa, lista para uma sequência variável de coisas similares. O ganho é hashability: uma tupla entra em uma chave de dicionário, um conjunto ou uma chave de lru_cache, todos os lugares onde uma lista não consegue ir. Deixe o tipo carregar esse contrato em vez de um comentário.
python
locations = {}
locations[(40, -74)] = "São Paulo"   # tupla como chave de dicionário, funciona
locations[[40, -74]] = "São Paulo"   # lista como chave de dicionário, TypeError

Desempacotamento

Desempacotamento puxa valores para fora de uma tupla e atribui cada um a seu próprio nome em uma única linha. O número de nomes deve combinar com o número de valores. Use * para capturar quaisquer itens restantes em uma lista.

Desempacotamento funciona em qualquer iterável: tuplas, listas, strings. A contagem de nomes-alvo deve combinar com o comprimento do iterável, a menos que um alvo com asterisco capture uma fatia de comprimento variável. Desigualdade levanta ValueError. Desempacotamento é a forma idiomática de consumir múltiplos valores de retorno de uma função.

Desempacotamento funciona em qualquer iterável (qualquer coisa que você pode fazer loop), não apenas tuplas, percorrendo o lado direito e ligando cada valor a seu nome-alvo em ordem. Um alvo com asterisco (*rest) absorve os itens restantes em uma list, então a contagem não precisa ser exata. Uma desigualdade simples de contagem levanta ValueError em tempo de execução. A forma que você mais usa é em um cabeçalho for: for name, score in pairs desempacota cada item conforme faz loop, o que lê muito melhor do que indexar cada par à mão.

JunoDesempacotamento Desempacotamento puxa cada valor para fora de uma tupla ou lista e lhe dá seu próprio nome em uma linha, como x, y = point. O número de nomes tem que combinar com o número de valores, a menos que você adicione um *rest para colher o que sobrar. Fez clique para mim o dia em que parei de escrever point[0] e point[1] em todo lugar.
JunoDesempacotamento Desempacotamento atribui cada valor a seu próprio nome em uma linha, e um alvo com asterisco como *rest captura uma fatia de comprimento variável. Contagens que não combinam levantam ValueError. É a forma limpa de pegar múltiplos valores de retorno de uma função em vez de indexar o resultado.
JunoDesempacotamento Desempacotamento roda em qualquer iterável e liga da esquerda para direita; *rest absorve a folga, uma desigualdade simples levanta ValueError. O spot onde mais vale a pena é o cabeçalho for, for name, score in pairs, que bate alcançar cada par por índice.
python
point = (10, 20)
x, y = point

print(x)   # 10
print(y)   # 20

first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
# head = 1, middle = [2, 3, 4], tail = 5

Tuplas nomeadas

Uma tupla nomeada é uma tupla onde cada posição tem um nome. Em vez de lembrar que point[0] é a coordenada x, você escreve point.x. Os valores ainda são imutáveis; você recebe nomes de atributo legíveis em vez de posições numéricas.

namedtuple gera uma classe que se comporta exatamente como uma tupla mas adiciona acesso a atributos nomeados. É mais leve que uma classe completa, imutável e autodocumentada. Use quando o acesso posicional de uma tupla simples exigiria um comentário para ser compreendido.

collections.namedtuple é uma fábrica de classes (uma função que constrói e lhe devolve uma marca nova classe para você usar). A classe que retorna é uma tuple com campos nomeados, então custa a mesma memória que uma tupla simples enquanto lê muito melhor. Você recebe _asdict() (transformá-la em um dicionário), _replace() (fazer uma cópia com um campo mudado, já que você não consegue mutá-la) e _fields de graça. Quando você precisa de mais, valores padrão ou anotações de tipo, typing.NamedTuple cobre o caso anotado, e dataclasses.dataclass é a escolha moderna uma vez que você quer métodos ou mutabilidade opcional.

JunoTuplas nomeadas Uma tupla nomeada dá a cada posição um nome, então você escreve point.x em vez de lembrar que point[0] é o x. Ainda é totalmente imutável e age como uma tupla normal em qualquer outro aspecto. Seu eu futuro lendo o código vai agradecer pelos nomes.
JunoTuplas nomeadasnamedtuple constrói uma classe tipo-tupla com acesso a atributos nomeados, imutável e autodocumentada, com menos peso que uma classe completa. Recorra a ela no momento em que as posições de uma tupla simples precisariam de um comentário para explicar. Além disso, dataclass é o passo moderno a seguir.
JunoTuplas nomeadasnamedtuple lhe dá uma subclasse tupla com campos nomeados no mesmo custo de memória, mais _replace() e _asdict() de graça. Uma vez que você quer padrões, métodos ou dicas de tipo real, pule para typing.NamedTuple ou dataclass em vez de lutar contra a fábrica.

namedtuple vive na biblioteca padrão, então precisa de uma importação primeiro: from collections import namedtuple. Importações recebem tratamento completo no capítulo Módulos.

python
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
Player = namedtuple("Player", ["name", "score", "level"])

p = Point(10, 20)
p.x    # 10
p.y    # 20

alice = Player("Maria", 87, 5)
alice.name    # "Maria"
alice.score   # 87

Conjuntos

Um conjunto é uma coleção de valores únicos sem ordem garantida. Adicionar o mesmo valor duas vezes não faz nada: um conjunto mantém apenas uma cópia de cada item. Use chaves para um conjunto com itens, ou set() para criar um conjunto vazio.

set é uma coleção desordenada que rejeita automaticamente duplicatas. Testes de pertencimento são O(1) independentemente do tamanho, o que o torna a ferramenta certa sempre que você precisa verificar se um valor existe em uma grande coleção. Nota: {} cria um dicionário vazio, não um conjunto vazio; use set() para isso.

Um set guarda valores únicos, hashable e é construído em uma tabela de dispersão, a mesma estrutura de busca por conteúdo de um dicionário. Isso lhe compra pertencimento, inserção e deleção O(1) em média (o custo fica plano conforme o conjunto cresce, em vez de subir com seu tamanho). Duas consequências para planejar: apenas valores hashable conseguem entrar, então int, str e tuple são bons mas list, dict e set não, e a ordem de iteração segue posições internas de dispersão, então nunca confie em ser estável. Uma armadilha de sintaxe: {} é um dicionário vazio, não um conjunto vazio; use set().

JunoConjuntos Um conjunto mantém apenas valores únicos, então adicionar algo que já está lá não faz absolutamente nada, nenhum erro, nenhuma duplicata. Construa um com chaves e itens, mas recorra a set() para um vazio, porque {} é secretamente um dicionário vazio. Essa última parte pega quase todo mundo.
JunoConjuntos Um conjunto descarta duplicatas automaticamente e testa pertencimento em O(1) não importa o tamanho, o que o torna a ferramenta certa para "isso está aqui?" em uma grande coleção. Observe a armadilha do conjunto vazio: {} é um dicionário, então use set().
JunoConjuntos Um conjunto é uma tabela de dispersão de valores únicos hashable: O(1) pertencimento, inserção e deleção, ao custo de apenas itens hashable entrarem e a ordem de iteração nunca ser estável. Não confie nessa ordem, e lembre-se que {} é um dicionário, então recorra a set() quando quiser um conjunto vazio.
python
tags = {"python", "iniciante", "tutorial"}
numbers = {1, 2, 3, 4, 5}
empty = set()    # NÃO {} (isso é um dicionário vazio)

Adicionar o mesmo valor duas vezes não muda o conjunto:

python
tags.add("python")   # tags não muda, "python" já está nele

Quando usar um conjunto

Conjuntos são a ferramenta certa para três coisas: remover duplicatas de uma lista, verificar rapidamente se algo está em uma grande coleção e comparar dois grupos para encontrar o que eles compartilham ou diferem.

Três casos de uso distintos dirigem o uso de conjuntos: deduplicação (automática na inserção), testes de pertencimento O(1) (versus O(n) para list) e álgebra de conjuntos (|, &, -, ^). Quando a coleção é grande e você verifica pertencimento frequentemente, a diferença de desempenho é substancial.

Três casos de uso saem direto da tabela de dispersão: unicidade (duplicatas rejeitadas na inserção), pertencimento O(1) (o custo de in fica plano conforme o conjunto cresce) e álgebra de conjuntos (|, &, -, ^). O teste de pertencimento é o que vale a pena em código real: in em um conjunto de 10.000 itens é tão rápido quanto em um conjunto de 10, onde a mesma verificação em uma lista é O(n) e fica mais lenta linearmente. Quando você se encontra fazendo x in some_list repetido contra uma grande lista, converter para conjunto uma vez é geralmente o conserto.

JunoQuando usar um conjunto Três trabalhos que conjuntos são bons: tirar duplicatas de uma lista, verificar rápido se algo está em uma grande coleção e comparar dois grupos para ver o que eles compartilham ou diferem. Se qualquer um desses for o que você está fazendo, um conjunto é provavelmente a ferramenta.
JunoQuando usar um conjunto Três drivers: deduplicação na inserção, pertencimento O(1) versus O(n) para uma lista e álgebra de conjuntos com `|`, `&`, `-`, `^`. Quanto maior a coleção e mais frequentemente você verifica `in`, mais o conjunto se afasta.
JunoQuando usar um conjunto Unicidade, `in` O(1) e álgebra de conjuntos, tudo saindo da tabela de dispersão. O ganho de pertencimento é o prático: `x in big_list` repetido é O(n) cada vez, então converter essa lista para conjunto uma vez é o conserto usual.
python
# Remover duplicatas de uma lista
raw = ["gato", "cachorro", "gato", "pássaro", "cachorro", "gato"]
unique = list(set(raw))   # ["gato", "cachorro", "pássaro"] (ordem não garantida)
python
# Verificação rápida de pertencimento
valid_codes = {"USD", "EUR", "GBP", "JPY"}
code = "EUR"

if code in valid_codes:    # busca instantânea, mesmo com milhares de códigos
    print("Válido")

Operações de conjunto

Conjuntos suportam as mesmas operações que você aprendeu em matemática: união (tudo em qualquer conjunto), interseção (apenas o que ambos os conjuntos compartilham) e diferença (o que um tem que o outro não tem). Python usa símbolos de operador para esses, e cada um tem um equivalente de método.

Os operadores de conjunto do Python espelham notação matemática: | para união, & para interseção, - para diferença, ^ para diferença simétrica. Cada operador tem uma forma de método (.union(), .intersection(), etc.) que também aceita qualquer iterável, não apenas conjuntos.

As formas de operador (|, &, -, ^) exigem que ambos os lados sejam conjuntos e levantam TypeError se qualquer um for outra coisa, como uma lista. As formas de método (.union(), .intersection() e o resto) são mais frouxas: aceitam qualquer iterável e convertem para você, então a.union([1, 2]) funciona onde a | [1, 2] falha. Essa diferença é a que lembrar quando uma operação explode. Existem também formas in-place (|=, &=, -=, ^=) que atualizam o conjunto esquerdo em vez de retornar um novo, o mesmo que chamar .update(), .intersection_update() e assim por diante.

JunoOperações de conjunto Quatro operações da aula de matemática: | é união (em qualquer um), & é interseção (em ambos), - é diferença (em um mas não no outro) e ^ é diferença simétrica (em um mas não em ambos). Cada uma também tem um método escrito como .union() se você preferir palavras a símbolos.
JunoOperações de conjunto| união, & interseção, - diferença, ^ diferença simétrica. As formas de método (.union() e companhia) fazem o mesmo mas aceitam qualquer iterável, não apenas um conjunto, o que os operadores não fazem.
JunoOperações de conjunto Operadores querem que ambos os lados sejam conjuntos e levantam TypeError caso contrário; as formas de método aceitam qualquer iterável, então a.union([1, 2]) funciona onde a | [1, 2] não funciona. As formas in-place (|=, &=) atualizam no lugar em vez de retornar um novo conjunto.
python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b    # {1, 2, 3, 4, 5, 6}   (união: tudo em qualquer um)
a & b    # {3, 4}               (interseção: apenas em ambos)
a - b    # {1, 2}               (diferença: em a mas não em b)
b - a    # {5, 6}               (diferença do outro lado)
a ^ b    # {1, 2, 5, 6}        (diferença simétrica: em um mas não em ambos)

Esses também têm formas de método: .union(), .intersection(), .difference(), .symmetric_difference().

Modificando conjuntos

Conjuntos são mutáveis. .add() adiciona um item. .update() adiciona vários de uma vez de qualquer lista ou outro iterável. .remove() deleta um item mas levanta um erro se não estiver lá. .discard() deleta silenciosamente se o item existe e faz nada se não existe.

.add() é O(1) em média. .update() aceita qualquer iterável e é equivalente a chamar .add() em um loop. .remove() levanta KeyError em uma falha, enquanto .discard() é a escolha segura quando a presença é incerta. .pop() remove um elemento arbitrário, não o "último" já que conjuntos não têm ordem.

.add(x) insere um item em O(1) em média; .update(iterable) adiciona muitos e é o mesmo que |=. O par que pega pessoas é .remove() versus .discard(): ambos deletam um item, mas .remove() levanta KeyError quando o item está ausente enquanto .discard() quietamente faz nada. Recorra a .discard() sempre que a presença for incerta, então um item faltante não é uma exceção que você precisa envolver. .pop() retorna e remove algum elemento, mas qual é indefinido: um conjunto não tem ordem, então nunca trate .pop() como pegando o item "último".

JunoModificando conjuntos.add() coloca um item, .update() adiciona um lote inteiro de uma lista ou outro iterável. O par para manter reto é remoção: .remove() erra se o item não estiver lá, enquanto .discard() encolhe os ombros e segue em frente. Quando você não tem certeza se está no conjunto, .discard() lhe poupa um erro aleatório.
JunoModificando conjuntos.add() para um, .update() para muitos de qualquer iterável. .remove() levanta KeyError em uma falha; .discard() é a versão segura que ignora. E .pop() puxa um elemento arbitrário, não um "último", já que conjuntos não têm ordem.
JunoModificando conjuntos.add() é O(1), .update() é |=. A armadilha é .remove() levantando KeyError onde .discard() fica quieto, então padrão para .discard() quando a presença for incerta. .pop() retorna um elemento indefinido; nunca leia como o último.
python
tags = {"python", "iniciante"}

tags.add("tutorial")          # adicionar um item
tags.update(["web", "api"])   # adicionar múltiplos itens de qualquer iterável
tags.remove("iniciante")      # remover, levanta KeyError se não encontrado
tags.discard("faltando")      # remover, nenhum erro se não encontrado
tags.pop()                    # remover e retornar um item arbitrário
tags.clear()                  # remover tudo

Use .discard() quando você não tem certeza se o item existe.

Conjuntos congelados

Um conjunto congelado é um conjunto que você não consegue modificar após criação. A razão principal para usar um: conjuntos congelados são hashable, então podem ser usados como chaves de dicionário ou armazenados dentro de outros conjuntos.

frozenset é o contraparte imutável de set. Suporta todas as operações de leitura e álgebra de conjuntos mas não mutação. Sua imutabilidade o torna hashable, significando que é válido como chave de dicionário ou como membro dentro de outro conjunto.

Um frozenset é um set que você não consegue mudar após criar. Porque nada dentro dele se move, recebe um hash estável e é ele próprio hashable, então pode ser uma chave de dicionário ou um membro de outro conjunto, nenhum dos quais um set simples consegue fazer. Tudo que lê ou retorna uma nova coleção ainda funciona (pertencimento, os operadores de álgebra de conjuntos), enquanto os métodos mutantes (add, remove e o resto) se foram. O caso que cabe limpamente: uma tabela de busca constante que não deve mudar enquanto o programa roda e pode precisar viver dentro de um dicionário ou conjunto.

JunoConjuntos congelados Um conjunto congelado é um conjunto que você não consegue mudar uma vez que está feito. Esse estado bloqueado é o que o torna hashable, então diferentemente de um conjunto regular você pode usar um como chave de dicionário ou enfiá-lo dentro de outro conjunto. Recorra a ele quando você tem um grupo fixo de valores permitidos que nunca deveria mudar.
JunoConjuntos congeladosfrozenset é o `set` imutável: todas as leituras e álgebra de conjuntos, nenhuma mutação. Sua imutabilidade compra hashability, então funciona como chave de dicionário ou membro de outro conjunto onde um `set` simples levanta. Encaixa bem para uma tabela de busca constante.
JunoConjuntos congeladosfrozenset tem um hash estável, então vai onde um `set` não consegue: chaves de dicionário, membros de outro conjunto. Leituras e álgebra de conjuntos ficam, métodos de mutação se foram. O uso limpo é uma tabela de busca constante que não deve mudar em tempo de execução.
python
valid_statuses = frozenset({"ativo", "pausado", "deletado"})
valid_statuses.add("arquivado")    # AttributeError, frozenset é imutável

Escolhendo a coleção certa

Quatro tipos, cada um com um papel claro. Pergunte o que você precisa fazer com os dados e a escolha certa geralmente segue.

A escolha entre tipos de coleção é sobre quais operações importam e quais restrições seus dados têm: mutabilidade, ordenação, tratamento de duplicatas e estratégia de busca.

A escolha é parte desempenho, parte significado. dict e set dão busca O(1) em média através de dispersão, então verificar pertencimento fica rápido conforme crescem. list e tuple dão acesso O(1) por índice mas pertencimento O(n), onde in fica mais lento com o tamanho. O outro eixo é o que o tipo sinaliza: uma tuple é um registro fixo cuja imutabilidade compra hashability, onde uma list é uma sequência de comprimento variável. Quando você precisa de uma coleção hashable para aninhar dentro de um dicionário ou conjunto, tuple e frozenset são os dois tipos built-in que conseguem.

JunoEscolhendo a coleção certa Quatro tipos, quatro trabalhos claros: listas para coisas ordenadas que você vai mudar, tuplas para registros fixos, conjuntos para valores únicos e verificações rápidas "isso está aqui?", dicionários para procurar coisas por nome. Pergunte o que você precisa fazer com os dados e a certa geralmente cai.
JunoEscolhendo a coleção certa Escolha no que os dados precisam: mutabilidade, ordem, se duplicatas importam e como você procura coisas. Lista para uma sequência ordenada que você muta, tupla para um registro fixo, conjunto para valores únicos e pertencimento O(1), dicionário para busca chave-valor.
JunoEscolhendo a coleção certa Metade desempenho, metade significado: `dict` e `set` dão busca O(1), `list` e `tuple` dão O(1) por índice mas pertencimento O(n). Uma `tuple` também sinaliza "registro fixo" e compra hashability. Quando você precisa de uma coleção hashable para aninhar, `tuple` e `frozenset` são suas duas opções.
listtuplesetdict
OrdenadaSimSimNãoSim (ordem de inserção)
MutávelSimNãoSimSim
DuplicatasSimSimNãoNão (chaves)
Acesso porÍndiceÍndicen/aChave
Use quandoSequência ordenada, mutávelRegistro fixoValores únicos, pertencimento rápidoBusca chave-valor

Uma regra de decisão rápida:

  • Precisa procurar algo por nome? → dict
  • Precisa de uma coleção ordenada que vai modificar? → list
  • Tem um grupo fixo de valores relacionados? → tuple
  • Precisa de valores únicos ou testes rápidos de pertencimento? → set

Na prática

Usando tuplas para armazenar registros fixos e um conjunto para rastrear valores únicos:

python
home = (-15.7975, -47.8919)   # latitude, longitude
office = (-15.7864, -47.8778)

home_lat, home_lon = home
print(f"Casa: {home_lat}, {home_lon}")

# Rastrear visitantes únicos com um conjunto
visitors = set()
visitors.add("maria")
visitors.add("joão")
visitors.add("maria")    # já está no conjunto, silenciosamente ignorado
visitors.add("carolina")

print(f"Visitantes únicos: {len(visitors)}")
print(f"maria visitou: {'maria' in visitors}")
print(f"pedro visitou:  {'pedro' in visitors}")

Usando conjuntos para rastrear o que já foi processado e computar o trabalho restante:

python
already_processed = {"relatorio_jan.csv", "relatorio_fev.csv"}
all_files = {"relatorio_jan.csv", "relatorio_fev.csv", "relatorio_mar.csv", "relatorio_abr.csv"}

to_process = all_files - already_processed
print(f"Arquivos para processar: {sorted(to_process)}")

for filename in sorted(to_process):
    print(f"Processando {filename}...")
    already_processed.add(filename)

print(f"Pronto. Total processado: {len(already_processed)}")

Usando frozenset para tabelas de busca constantes e demonstrando testes de pertencimento O(1) com álgebra de conjuntos:

python
ALLOWED_METHODS = frozenset({"GET", "POST", "PUT", "PATCH", "DELETE"})
SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})

# Álgebra de conjunto em frozensets retorna um conjunto regular
unsafe_allowed = ALLOWED_METHODS - SAFE_METHODS
print(f"Métodos não-seguros permitidos: {unsafe_allowed}")

# frozenset é hashable, então pode ser armazenado em um conjunto (um conjunto simples não consegue)
method_groups = {
    frozenset({"GET", "HEAD", "OPTIONS"}),
    frozenset({"POST", "PUT", "PATCH"}),
    frozenset({"DELETE"}),
}
print(f"Grupos de método: {len(method_groups)}")

method = "POST"
print(f"Permitido: {method in ALLOWED_METHODS}")
print(f"Seguro:    {method in SAFE_METHODS}")

frozenset carrega busca O(1) e pode ser armazenado em qualquer lugar onde um tipo hashable é necessário. Álgebra de conjunto em dois objetos frozenset retorna um set simples; envolva o resultado em frozenset() para mantê-lo imutável.