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

Tuplas e sets ​

Você já conhece listas. O Python tem mais dois tipos de coleção que resolvem problemas que as listas não conseguem. Tuplas guardam um grupo fixo de valores que nunca vai mudar. Sets guardam apenas valores únicos e permitem verificar se um item pertence à coleção instantaneamente, não importa o tamanho dela.

O conjunto de ferramentas de coleção do Python tem quatro tipos. Listas e dicts resolvem a maioria dos casos gerais. Tuplas e sets resolvem os casos específicos: registros fixos onde a imutabilidade é uma vantagem, e coleções de valores únicos onde a prioridade é a verificação de pertencimento em O(1).

Além de list e dict, o Python oferece tuple (uma sequência de tamanho fixo que você não pode alterar depois de criada) e set (uma coleção não ordenada de valores únicos, construída sobre uma tabela hash, a mesma estrutura de busca por conteúdo que torna o dict rápido). A divisão que orienta cada decisão a seguir é esta: uma tupla é hashable (seu conteúdo pode ser reduzido a um único número, o que permite usá-la como chave de dict ou membro de set), uma lista não é. Escolha com base no que você precisa que a coleção faça, não em qual delas você usa por hábito.

Tuplas ​

Uma tupla é um grupo ordenado de valores que não pode ser alterado depois de criado. Parênteses definem uma tupla, mas eles são opcionais. A vírgula é o que de fato torna algo uma tupla. Uma tupla de um único item exige uma vírgula ao 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 os seus elementos também são, o que abre casos de uso que listas não conseguem cobrir: chaves de dict, membros de set e registros de estrutura fixa.

Uma tuple é uma sequência de tamanho fixo que você não pode alterar depois que ela existe. Como nada dentro dela se move, o Python consegue calcular um único hash para ela (um número derivado do seu conteúdo), desde que cada elemento também seja hashable — é isso que permite que uma tupla sirva como chave de dict ou membro de set, algo que uma lista não pode fazer. Indexação e slicing funcionam exatamente como em uma lista; não existe atribuição por item, então point[0] = 99 gera um TypeError no momento em que essa linha é executada. A forma de um único item, (42,), precisa da vírgula ao final. Sem ela, os parênteses só agrupam e você recebe de volta o valor puro 42.

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

Acessar por índice funciona exatamente como em uma lista. Tentar alterar um item gera um TypeError:

Indexação, slicing e índices negativos funcionam de forma idêntica a listas. Qualquer tentativa de atribuição por índice gera TypeError; isso é intencional, não uma limitação.

Ler de uma tupla, por índice, índice negativo ou slice, se comporta exatamente como em uma lista. Escrever é o que muda: não existe atribuição por item, então point[0] = 99 gera TypeError no instante em que essa linha é executada. É justamente por isso que se escolhe uma tupla em vez de uma lista: é um valor no qual você e qualquer pessoa que ler o código depois pode confiar que não vai mudar por debaixo dos panos.

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 pode alterar depois de criá-lo. A vírgula é o que a cria, não os parênteses, e é por isso que (42,) precisa daquela vírgula solitária ao final. Tente reatribuir um item e você recebe um TypeError, e essa sensação de "travado" é exatamente o motivo de escolher uma tupla.
JunoTuplas Tuplas são sequências imutáveis, e é a vírgula que as cria, não os parênteses. Essa imutabilidade é uma vantagem: ela torna a tupla hashable quando seu conteúdo também é, o que permite usá-la como chave de dict ou membro de set, algo que uma lista não pode. A atribuição por item gera TypeError de propósito.
JunoTuplas Ler uma tupla é igual a ler uma lista; escrever é o que falta, então point[0] = 99 gera TypeError em tempo de execução. Aproveite isso: uma tupla é o valor que você passa por aí quando precisa da garantia de que nada mais adiante vai reescrevê-lo. A pegadinha do (42,) de um único item pega todo mundo pelo menos uma vez.

Quando usar uma tupla ​

Use uma tupla quando você tiver 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 ("Alice", 87). A estrutura fixa sinaliza para quem lê o código que esse grupo é tratado como uma única unidade.

Tuplas comunicam estrutura fixa: um grupo de valores onde a posição carrega significado e o grupo é tratado como uma unidade. Sua propriedade de ser hashable 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.

Use uma tupla quando tiver um registro de formato fixo: um número conhecido de campos em que cada posição significa algo específico. Como uma tupla é hashable (seu conteúdo se reduz a um único número), ela se encaixa em qualquer lugar que exija um valor hashable: uma chave de dict, um membro de set, ou os argumentos armazenados em cache por functools.lru_cache, que memoriza os resultados de uma função com base nos argumentos usados na chamada. O sinal semântico também é diferente do 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 semelhantes cujo tamanho pode variar."

JunoQuando usar uma tupla Use uma tupla quando tiver um grupinho fixo em que cada posição significa algo: uma coordenada, uma cor (r, g, b), um par nome-e-pontuação. Como tuplas são hashable, você pode até usar uma como chave de dict. Uma lista não consegue fazer isso, e é isso que confunde as pessoas na primeira vez que tentam.
JunoQuando usar uma tupla Uma tupla sinaliza estrutura fixa: a posição carrega significado e o grupo se move como uma única unidade. Essa é a diferença em relação a uma lista, que diz "uma sequência cujo tamanho pode variar." E, como tuplas são hashable, elas funcionam como chaves de dict, onde listas gerariam TypeError.
JunoQuando usar uma tupla Tupla para um registro de formato fixo, lista para uma sequência de tamanho variável com coisas semelhantes. O ganho é a propriedade de ser hashable: uma tupla se encaixa como chave de dict, membro de set, ou chave de um lru_cache, todos os lugares onde uma lista não pode entrar. Deixe o tipo carregar esse contrato em vez de um comentário.
python
locations = {}
locations[(40, -74)] = "Nova York"   # tupla como chave de dict, funciona
locations[[40, -74]] = "Nova York"   # lista como chave de dict, TypeError

Desempacotamento ​

Desempacotar extrai valores de uma tupla e atribui cada um a seu próprio nome em uma única linha. O número de nomes precisa corresponder ao número de valores. Use * para capturar os itens restantes em uma lista.

Desempacotar funciona em qualquer iterável: tuplas, listas, strings. A quantidade de nomes de destino precisa corresponder ao tamanho do iterável, a menos que um destino com asterisco capture uma parte de tamanho variável. Uma diferença de quantidade gera ValueError. Desempacotar é a forma idiomática de consumir múltiplos valores de retorno de uma função.

Desempacotar funciona em qualquer coisa iterável (qualquer coisa sobre a qual você pode fazer um loop), não só em tuplas, percorrendo o lado direito e vinculando cada valor ao seu nome de destino em ordem. Um destino com asterisco (*rest) absorve os itens restantes em uma list, então a quantidade não precisa mais corresponder exatamente. Uma diferença de quantidade simples gera ValueError em tempo de execução. A forma que você mais vai usar é no cabeçalho de um for: for name, score in pairs desempacota cada item conforme o loop avança, o que fica muito mais limpo do que indexar cada par manualmente.

JunoDesempacotamento Desempacotar extrai cada valor de uma tupla ou lista e dá a ele seu próprio nome em uma única linha, como x, y = point. O número de nomes precisa corresponder ao número de valores, a menos que você adicione um *rest para recolher o que sobrar. Isso fez sentido pra mim no dia em que parei de escrever point[0] e point[1] em todo lugar.
JunoDesempacotamento Desempacotar atribui cada valor a seu próprio nome em uma linha, e um destino com asterisco como *rest captura uma parte de tamanho variável. Quantidades que não coincidem geram ValueError. É a forma limpa de pegar múltiplos valores de retorno de uma função em vez de indexar o resultado.
JunoDesempacotamento Desempacotar funciona em qualquer iterável e vincula da esquerda para a direita; *rest absorve a sobra, uma diferença simples de quantidade gera ValueError. O lugar onde isso compensa mais é no cabeçalho do for, for name, score in pairs, que supera indexar cada par manualmente.
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 em que cada posição tem um nome. Em vez de lembrar que point[0] é a coordenada x, você escreve point.x. Os valores continuam imutáveis; você só ganha 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 por nome de atributo. É mais leve que uma classe completa, imutável e autoexplicativa. Use quando o acesso posicional de uma tupla simples exigiria um comentário para ser entendido.

collections.namedtuple é uma fábrica de classes (uma função que constrói e devolve uma classe nova para você usar). A classe que ela retorna é uma tuple com campos nomeados, então custa a mesma memória que uma tupla simples enquanto fica muito mais legível. Você ganha _asdict() (transforma em dict), _replace() (faz uma cópia com um campo alterado, já que você não pode mutá-la) e _fields de graça. Quando precisar de mais — valores padrão ou anotações de tipo — typing.NamedTuple cobre o caso anotado, e dataclasses.dataclass é a escolha moderna quando você quer métodos ou mutabilidade opcional.

JunoTuplas nomeadas Uma tupla nomeada dá um nome a cada posição, então você escreve point.x em vez de lembrar que point[0] é o x. Ela continua totalmente imutável e se comporta como uma tupla comum em tudo o mais. Seu "eu do futuro" vai agradecer pelos nomes quando reler o código.
JunoTuplas nomeadasnamedtuple constrói uma classe parecida com tupla com acesso por nome de atributo, imutável e autoexplicativa, com menos peso do que uma classe completa. Use no momento em que as posições de uma tupla simples exigiriam um comentário para explicar. Além disso, dataclass é o passo moderno seguinte.
JunoTuplas nomeadasnamedtuple te dá uma subclasse de tupla com campos nomeados pelo mesmo custo de memória, mais _replace() e _asdict() de graça. Quando você quiser valores padrão, métodos ou anotações de tipo de verdade, vá para typing.NamedTuple ou dataclass em vez de forçar a fábrica.

namedtuple faz parte da biblioteca padrão, então precisa de um import antes: from collections import namedtuple. Imports recebem um tratamento completo no capítulo de 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("Alice", 87, 5)
alice.name    # "Alice"
alice.score   # 87

Sets ​

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

set é uma coleção não ordenada que rejeita duplicatas automaticamente. A verificação de pertencimento é O(1) independentemente do tamanho, o que faz dele a ferramenta certa sempre que você precisa checar se um valor existe em uma coleção grande. Atenção: {} cria um dict vazio, não um set vazio; use set() para isso.

Um set guarda valores únicos e hashable, e é construído sobre uma tabela hash, a mesma estrutura de busca por conteúdo de um dict. Isso te dá pertencimento, inserção e remoção em O(1) na média (o custo se mantém constante conforme o set cresce, em vez de aumentar com o tamanho). Duas consequências para ter em mente: só valores hashable podem entrar, então int, str e tuple funcionam, mas list, dict e set não; e a ordem de iteração segue posições internas de hash, então nunca conte com ela sendo estável. Uma pegadinha de sintaxe: {} é um dict vazio, não um set vazio; use set().

JunoSets Um set mantém apenas valores únicos, então adicionar algo que já está lá dentro não faz absolutamente nada, sem erro, sem duplicata. Construa um com chaves e itens, mas use set() para um vazio, porque {} é secretamente um dict vazio. Esse último detalhe pega quase todo mundo.
JunoSets Um set 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 dentro?" em uma coleção grande. Atenção à pegadinha do set vazio: {} é um dict, então use set().
JunoSets Um set é uma tabela hash de valores únicos e hashable: pertencimento, inserção e remoção em O(1), ao custo de que só itens hashable podem entrar e a ordem de iteração nunca é estável. Não conte com essa ordem, e lembre que {} é um dict, então use set() quando quiser um set vazio.
python
tags = {"python", "beginner", "tutorial"}
numbers = {1, 2, 3, 4, 5}
empty = set()    # NÃO {} (isso é um dict vazio)

Adicionar o mesmo valor duas vezes não altera o set:

python
tags.add("python")   # tags permanece igual, "python" já estava presente

Quando usar um set ​

Sets são a ferramenta certa para três coisas: remover duplicatas de uma lista, checar rapidamente se algo está em uma coleção grande, e comparar dois grupos para descobrir o que eles têm em comum ou de diferente.

Três casos de uso distintos motivam o uso de sets: deduplicação (automática na inserção), verificação de pertencimento em O(1) (contra O(n) para list), e álgebra de conjuntos (|, &, -, ^). Quando a coleção é grande e você checa pertencimento com frequência, a diferença de desempenho é significativa.

Três casos de uso vêm direto da tabela hash: unicidade (duplicatas rejeitadas na inserção), pertencimento em O(1) (o custo de in se mantém constante conforme o set cresce), e álgebra de conjuntos (|, &, -, ^). O teste de pertencimento é o que traz mais ganho em código real: in em um set de 10.000 itens é tão rápido quanto em um set de 10, enquanto o mesmo teste em uma lista é O(n) e fica mais lento linearmente. Quando você se pegar fazendo x in some_list repetidamente contra uma lista grande, converter essa lista em um set uma única vez costuma ser a solução.

JunoQuando usar um set Três tarefas em que sets se destacam: eliminar duplicatas de uma lista, checar rápido se algo está em uma coleção grande, e comparar dois grupos para ver o que eles têm em comum ou de diferente. Se é isso que você está fazendo, um set provavelmente é a ferramenta certa.
JunoQuando usar um set Três motivadores: deduplicação na inserção, pertencimento em O(1) contra O(n) de uma lista, e álgebra de conjuntos com |, &, -, ^. Quanto maior a coleção e mais vezes você checa in, mais o set se destaca.
JunoQuando usar um set Unicidade, in em O(1), e álgebra de conjuntos, tudo derivado da tabela hash. O ganho de pertencimento é o prático: x in big_list repetido é O(n) a cada vez, então converter essa lista em um set uma única vez costuma ser a solução.
python
# Remove duplicatas de uma lista
raw = ["cat", "dog", "cat", "bird", "dog", "cat"]
unique = list(set(raw))   # ["cat", "dog", "bird"] (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("Valid")

Operações de conjunto ​

Sets suportam as mesmas operações que você aprendeu em matemática: união (tudo que está em qualquer um dos dois sets), interseção (só o que os dois têm em comum) e diferença (o que um tem e o outro não). O Python usa símbolos de operador para isso, e cada um tem um método equivalente.

Os operadores de set do Python espelham a 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 só sets.

As formas com operador (|, &, -, ^) exigem que os dois lados sejam sets e geram TypeError se algum deles for outra coisa, como uma lista. As formas de método (.union(), .intersection() e as demais) são mais flexíveis: aceitam qualquer iterável e o convertem por você, então a.union([1, 2]) funciona onde a | [1, 2] falha. Essa diferença é a que vale lembrar quando uma operação der erro. Também existem formas no local (|=, &=, -=, ^=) que atualizam o set da esquerda 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 dos dois), & é interseção (nos dois), - é diferença (em um mas não no outro), e ^ é diferença simétrica (em um mas não nos dois). Cada uma também tem um método por extenso, 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 só um set, o que os operadores não permitem.
JunoOperações de conjunto Os operadores exigem que os dois lados sejam sets e geram 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 no local (|=, &=) atualizam no lugar em vez de retornar um novo set.
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 dos dois)
a & b    # {3, 4}               (interseção: só nos dois)
a - b    # {1, 2}               (diferença: em a mas não em b)
b - a    # {5, 6}               (diferença no outro sentido)
a ^ b    # {1, 2, 5, 6}        (diferença simétrica: em um mas não nos dois)

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

Modificando sets ​

Sets são mutáveis. .add() adiciona um item. .update() adiciona vários de uma vez a partir de qualquer lista ou outro iterável. .remove() apaga um item, mas gera um erro se ele não estiver lá. .discard() apaga silenciosamente se o item existir e não faz nada se não existir.

.add() é O(1) na média. .update() aceita qualquer iterável e é equivalente a chamar .add() dentro de um loop. .remove() gera KeyError quando não encontra o item, enquanto .discard() é a escolha segura quando a presença é incerta. .pop() remove um elemento arbitrário, não o "último", já que sets não têm ordem.

.add(x) insere um item em O(1) na média; .update(iterable) adiciona vários e é o mesmo que |=. O par que costuma confundir é .remove() versus .discard(): os dois apagam um item, mas .remove() gera KeyError quando o item está ausente, enquanto .discard() simplesmente não faz nada. Use .discard() sempre que a presença for incerta, para que um item ausente não seja uma exceção que você precisa tratar. .pop() retorna e remove algum elemento, mas qual deles não é definido: um set não tem ordem, então nunca trate .pop() como se estivesse pegando o "último" item.

JunoModificando sets.add() coloca um item, .update() adiciona um lote inteiro a partir de uma lista ou outro iterável. O par para não confundir é a remoção: .remove() dá erro se o item não estiver lá, enquanto .discard() dá de ombros e segue em frente. Quando você não tem certeza se o item está no set, .discard() te poupa de um erro desnecessário.
JunoModificando sets.add() para um item, .update() para vários a partir de qualquer iterável. .remove() gera KeyError quando não encontra; .discard() é a versão segura que ignora isso. E .pop() retira um elemento arbitrário, não um "último", já que sets não têm ordem.
JunoModificando sets.add() é O(1), .update() é |=. A pegadinha é .remove() gerar KeyError onde .discard() fica quieto, então prefira .discard() quando a presença for incerta. .pop() retorna um elemento indefinido; nunca leia isso como o último.
python
tags = {"python", "beginner"}

tags.add("tutorial")          # adiciona um item
tags.update(["web", "api"])   # adiciona múltiplos itens de qualquer iterável
tags.remove("beginner")       # remove, gera KeyError se não encontrar
tags.discard("missing")       # remove, sem erro se não encontrar
tags.pop()                    # remove e retorna um item arbitrário
tags.clear()                  # remove tudo

Use .discard() quando não tiver certeza se o item existe.

Frozen sets ​

Um frozen set é um set que você não pode modificar depois de criado. O principal motivo para usar um: frozen sets são hashable, então podem ser usados como chaves de dicionário ou armazenados dentro de outros sets.

frozenset é o equivalente imutável de set. Ele suporta todas as operações de leitura e a álgebra de conjuntos, mas não permite mutação. Sua imutabilidade o torna hashable, o que significa que ele é válido como chave de dict ou como membro dentro de outro set.

Um frozenset é um set que você não pode alterar depois de criado. Como nada dentro dele se move, ele tem um hash estável e é ele próprio hashable, então pode ser chave de dict ou membro de outro set, nada disso um set comum consegue fazer. Tudo que lê ou retorna uma nova coleção continua funcionando (pertencimento, os operadores de álgebra de conjuntos), enquanto os métodos de mutação (add, remove e os demais) desaparecem. O caso em que ele se encaixa bem: uma tabela de consulta constante que não pode mudar enquanto o programa roda e pode precisar viver dentro de um dict ou set.

JunoFrozen sets Um frozen set é um set que você não pode alterar depois de criado. Esse estado travado é o que o torna hashable, então, diferente de um set comum, você pode usar um como chave de dict ou colocá-lo dentro de outro set. Use quando tiver um grupo fixo de valores permitidos que nunca deve mudar.
JunoFrozen setsfrozenset é o set imutável: todas as leituras e a álgebra de conjuntos, sem nenhuma mutação. Sua imutabilidade compra a propriedade de ser hashable, então ele funciona como chave de dict ou membro de outro set, onde um set comum geraria erro. Bom encaixe para uma tabela de consulta constante.
JunoFrozen setsfrozenset tem um hash estável, então vai onde um set não pode ir: chaves de dict, membros de outro set. Leituras e álgebra de conjuntos continuam, os métodos de mutação desaparecem. O uso limpo é uma tabela de consulta constante que não pode mudar em tempo de execução.
python
valid_statuses = frozenset({"active", "paused", "deleted"})
valid_statuses.add("archived")    # 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 aparece.

A escolha entre tipos de coleção depende de 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 é em parte sobre desempenho, em parte sobre significado. dict e set oferecem busca em O(1) na média por meio de hashing, então checar pertencimento continua rápido conforme eles crescem. list e tuple oferecem acesso por índice em O(1), mas pertencimento em 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 a propriedade de ser hashable, enquanto uma list é uma sequência de tamanho variável. Quando você precisa de uma coleção hashable para aninhar dentro de um dict ou set, tuple e frozenset são os dois tipos nativos capazes de fazer isso.

JunoEscolhendo a coleção certa Quatro tipos, quatro papéis claros: listas para coisas ordenadas que você vai mudar, tuplas para registros fixos, sets para valores únicos e checagens rápidas de "isso está aqui?", dicts para buscar coisas pelo nome. Pergunte o que você precisa fazer com os dados, e a escolha certa geralmente aparece.
JunoEscolhendo a coleção certa Escolha com base no que os dados precisam: mutabilidade, ordem, se duplicatas importam, e como você busca as coisas. Lista para uma sequência ordenada que você muta, tupla para um registro fixo, set para valores únicos e pertencimento em O(1), dict para busca por chave-valor.
JunoEscolhendo a coleção certa Meio desempenho, meio significado: dict e set dão busca em O(1), list e tuple dão O(1) por índice, mas O(n) para pertencimento. Uma tuple também sinaliza "registro fixo" e compra a propriedade de ser hashable. Quando você precisa de uma coleção hashable para aninhar, tuple e frozenset são suas duas opções.
listtuplesetdict
OrdenadoSimSimNãoSim (ordem de inserção)
MutávelSimNãoSimSim
DuplicatasSimSimNãoNão (chaves)
Acesso porÍndiceÍndicen/aChave
Use quandoSequência ordenada e alterávelRegistro fixoValores únicos, pertencimento rápidoBusca por chave-valor

Uma regra rápida de decisão:

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

Na prática ​

Usando tuplas para armazenar registros fixos e um set para acompanhar valores únicos:

python
home = (51.5074, -0.1278)   # latitude, longitude
office = (51.5155, -0.0922)

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

# Acompanha visitantes únicos com um set
visitors = set()
visitors.add("alice")
visitors.add("bob")
visitors.add("alice")    # já está no set, ignorado silenciosamente
visitors.add("carol")

print(f"Unique visitors: {len(visitors)}")
print(f"alice visited: {'alice' in visitors}")
print(f"dave visited:  {'dave' in visitors}")

Usando sets para acompanhar o que já foi processado e calcular o trabalho restante:

python
already_processed = {"report_jan.csv", "report_feb.csv"}
all_files = {"report_jan.csv", "report_feb.csv", "report_mar.csv", "report_apr.csv"}

to_process = all_files - already_processed
print(f"Files to process: {sorted(to_process)}")

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

print(f"Done. Total processed: {len(already_processed)}")

Usando frozenset para tabelas de consulta constantes e demonstrando a verificação de pertencimento em O(1) com álgebra de conjuntos:

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

# Álgebra de conjuntos em frozensets retorna um set comum
unsafe_allowed = ALLOWED_METHODS - SAFE_METHODS
print(f"Non-safe allowed methods: {unsafe_allowed}")

# frozenset é hashable, então pode ser armazenado em um set (um set comum não pode)
method_groups = {
    frozenset({"GET", "HEAD", "OPTIONS"}),
    frozenset({"POST", "PUT", "PATCH"}),
    frozenset({"DELETE"}),
}
print(f"Method groups: {len(method_groups)}")

method = "POST"
print(f"Allowed: {method in ALLOWED_METHODS}")
print(f"Safe:    {method in SAFE_METHODS}")

frozenset mantém busca em O(1) e pode ser armazenado em qualquer lugar que exija um tipo hashable. A álgebra de conjuntos em dois objetos frozenset retorna um set comum; envolva o resultado em frozenset() para mantê-lo imutável.