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

Tuplas e conjuntos

Você 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. Conjuntos guardam apenas valores únicos e permitem verificar a pertinência instantaneamente, não importa o tamanho da coleção.

O kit de coleções do Python tem quatro tipos. Listas e dicionários cobrem a maioria dos casos gerais. Tuplas e conjuntos resolvem os específicos: registros fixos onde a imutabilidade é uma vantagem, e coleções de valores únicos onde o teste de pertinência O(1) é a prioridade.

Além de list e dict, o Python fornece tuple (sequência imutável de comprimento fixo) e set (coleção não ordenada de objetos hasháveis únicos, baseada em tabela hash). Cada uma tem um modelo de memória distinto, características de hashabilidade e perfil de desempenho que vale conhecer antes de escolher.

Tuplas

Uma tupla é um grupo ordenado de valores que não pode ser alterado após sua criação. Parênteses definem uma tupla, mas são opcionais. A vírgula é o que de fato faz dela uma tupla. Uma tupla de um único item exige uma vírgula no final.

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

tuple é uma sequência imutável apoiada por um array C de tamanho fixo. __hash__ é calculado a partir dos hashes dos elementos quando todos são hasháveis, tornando tuplas válidas como chaves de dicionário e membros de conjuntos. __getitem__ suporta inteiros e fatias; __setitem__ não é implementado, portanto qualquer tentativa de mutação levanta TypeError. A forma de item único (42,) exige a vírgula final; sem ela, os parênteses são apenas agrupamento.

python
point      = (10, 20)
rgb        = (255, 128, 0)
dimensions = (1920, 1080)
single     = (42,)            # vírgula no 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 dela uma tupla

O acesso por índice funciona exatamente como em uma lista. Tentar alterar um item levanta um TypeError:

Indexação, fatiamento e índices negativos funcionam de forma idêntica às listas. Qualquer tentativa de atribuir via índice levanta TypeError; isso é intencional, não uma limitação.

__getitem__ com inteiros e objetos slice segue as mesmas regras de limitação que list. Não há __setitem__: o tipo tupla não o registra, então TypeError é levantado em tempo de execução, não em tempo de parsing.

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

point[0] = 99    # TypeError: 'tuple' object does not support item assignment

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 ("João", 87). A estrutura fixa sinaliza para quem lê o código que este 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 hashabilidade as torna válidas como chaves de dicionário, o que listas não podem ser. O contrato que uma tupla sinaliza é: estes valores pertencem juntos e não devem mudar.

Tuplas são o tipo idiomático para registros de aridade fixa. Sua hashabilidade as torna utilizáveis onde quer que Hashable seja exigido: chaves de dicionário, membros de conjunto, assinaturas de chamada em functools.lru_cache. O contrato semântico difere de uma lista: tuplas representam um registro heterogêneo onde a posição tem significado, listas representam uma sequência homogênea onde o tamanho e a ordem podem variar.

python
locations = {}
locations[(40, -74)] = "Nova York"   # tupla como chave de dicionário, funciona
locations[[40, -74]] = "Nova York"   # lista como chave de dicionário, TypeError

Desempacotamento

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

O desempacotamento funciona em qualquer iterável: tuplas, listas, strings. A contagem de nomes de destino deve corresponder ao comprimento do iterável, a menos que um destino com asterisco capture uma fatia de tamanho variável. Incompatibilidade levanta ValueError. O desempacotamento é a forma idiomática de consumir múltiplos valores de retorno de uma função.

O desempacotamento chama __iter__ no lado direito e vincula cada valor produzido ao nome de destino correspondente. Um destino com asterisco (*rest) coleta os itens restantes em uma list. Incompatibilidade levanta ValueError em tempo de execução. O desempacotamento estendido também funciona dentro de cabeçalhos for: for x, y in list_of_pairs desempacota cada item de iteração.

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 continuam imutáveis; você apenas ganha nomes de atributos legíveis em vez de posições numéricas.

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

collections.namedtuple é uma fábrica de classes: gera uma subclasse de tuple em tempo de execução com acesso por atributo nomeado compilado nela. A classe gerada inclui _asdict(), _replace() e _fields. O consumo de memória é idêntico ao de uma tupla simples. Para mais controle (valores padrão, anotações de tipo, mutabilidade opcional), dataclasses.dataclass é a alternativa moderna; para tuplas com anotação de tipo, typing.NamedTuple é idiomático.

Importação de tupla nomeada

namedtuple está na biblioteca padrão do Python, mas precisa ser importada. A linha from collections import namedtuple é a primeira importação deste curso. Importações são cobertas integralmente 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("João", 87, 5)
alice.name    # "João"
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 não ordenada que rejeita duplicatas automaticamente. O teste de pertinência é O(1) independentemente do tamanho, o que o torna a ferramenta certa sempre que você precisar verificar se um valor existe em uma coleção grande. Observação: {} cria um dicionário vazio, não um conjunto vazio; use set() para isso.

set é uma coleção apoiada por tabela hash de objetos hasháveis únicos. Teste de pertinência, inserção e remoção são todos O(1) em média. A ordem de iteração reflete posições internas do hash e não é estável entre execuções. Apenas objetos hasháveis podem ser membros: int, str, tuple sim; list, dict, set não. {} é interpretado como literal de dicionário vazio, não conjunto.

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 fica inalterado, "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 descobrir o que compartilham ou em que diferem.

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

Os três casos de uso canônicos dos conjuntos mapeiam diretamente para propriedades da tabela hash: unicidade (rejeição de duplicatas na inserção), __contains__ O(1) em média (consulta hash), e álgebra de conjuntos (operadores estilo bit-a-bit chamando métodos dunder). O teste de pertinência O(1) é o mais importante na prática: in em um conjunto de 10.000 itens é tão rápido quanto em um conjunto de 10.

python
# Remove duplicatas de uma lista
raw    = ["gato", "cachorro", "gato", "passaro", "cachorro", "gato"]
unique = list(set(raw))   # ["gato", "cachorro", "passaro"] (ordem não garantida)
python
# Verificação rápida de pertinência
valid_codes = {"USD", "EUR", "GBP", "JPY"}
code        = "EUR"

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

Operações de conjuntos

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

Os operadores de conjunto 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 apenas conjuntos.

Os operadores de conjunto chamam métodos dunder: | chama __or__, & chama __and__, - chama __sub__, ^ chama __xor__. Os operadores exigem que ambos os operandos sejam conjuntos e levantam TypeError caso contrário. As formas de método aceitam qualquer iterável e o convertem internamente. As formas in-place (|=, &=, -=, ^=) modificam o operando esquerdo, equivalentes a .update(), .intersection_update(), etc.

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 nos dois)
a - b    # {1, 2}               (diferença: em a mas não em b)
b - a    # {5, 6}               (diferença no sentido contrário)
a ^ b    # {1, 2, 5, 6}        (diferença simétrica: em um mas não em ambos)

Estes 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 a partir de qualquer lista ou outro iterável. .remove() apaga um item, mas levanta 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) em média. .update() aceita qualquer iterável e é equivalente a chamar .add() em loop. .remove() levanta KeyError em caso de ausência, espelhando dict.__delitem__. .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) faz hash de x e o insere na tabela: O(1) em média. .update(iterable) é equivalente a |=. .remove() levanta KeyError em caso de ausência. .discard() faz primeiro uma consulta hash e pula a remoção em caso de ausência. .pop() remove um elemento arbitrário determinado pelo estado interno da tabela hash, não pela ordem de inserção.

python
tags = {"python", "iniciante"}

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

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

Conjuntos congelados

Um conjunto congelado (frozen set) é um conjunto que não pode ser modificado após a criação. A principal razão para usá-lo: conjuntos congelados são hasháveis, então podem ser usados como chaves de dicionário ou armazenados dentro de outros conjuntos.

frozenset é a 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 hashável, significando que é válido como chave de dicionário ou como membro dentro de outro conjunto.

frozenset implementa __hash__ calculado a partir de uma redução ordenada dos hashes dos elementos, fornecendo um valor de hash estável. Todos os operadores e métodos de álgebra de conjuntos que retornam novas coleções são suportados; métodos de mutação (add, remove, etc.) não são definidos. frozenset é o tipo certo para uma tabela de consulta constante que não deve mudar em tempo de execução e pode precisar ser usada como chave de dicionário.

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 se torna óbvia.

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 consulta.

A escolha da coleção é uma decisão de desempenho e semântica. dict e set oferecem consulta O(1) em média via hashing. list e tuple oferecem acesso indexado O(1), mas teste de pertinência O(n). A imutabilidade de tuple proporciona hashabilidade. frozenset e tuple são os dois tipos compostos hasháveis na biblioteca padrão.

listtuplesetdict
OrdenadoSimSimNãoSim (ordem de inserção)
MutávelSimNãoSimSim
DuplicatasSimSimNãoNão (chaves)
Acesso porÍndiceÍndicen/aChave
Use quandoSequência ordenada e modificávelRegistro fixoValores únicos, pertinência rápidaConsulta chave-valor

Uma regra rápida de decisão:

  • Precisa consultar algo por 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 rápidos de pertinência? → set

Na prática

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

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

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

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

print(f"Visitantes únicos: {len(visitors)}")
print(f"joão visitou: {'joão' in visitors}")
print(f"ana visitou:  {'ana' in visitors}")

Usando conjuntos para rastrear 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"Arquivos a processar: {sorted(to_process)}")

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

print(f"Concluído. Total processado: {len(already_processed)}")

Usando frozenset para tabelas de consulta constantes e demonstrando teste de pertinência 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 conjunto comum
unsafe_allowed = ALLOWED_METHODS - SAFE_METHODS
print(f"Métodos permitidos não seguros: {unsafe_allowed}")

# frozenset é hashável, então pode ser armazenado em um conjunto (um conjunto comum não pode)
method_groups = {
    frozenset({"GET", "HEAD", "OPTIONS"}),
    frozenset({"POST", "PUT", "PATCH"}),
    frozenset({"DELETE"}),
}
print(f"Grupos de métodos: {len(method_groups)}")

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

frozenset oferece consulta O(1) e pode ser armazenado em qualquer lugar onde um tipo hashável seja exigido. Álgebra de conjuntos em dois objetos frozenset retorna um set comum; envolva o resultado em frozenset() para mantê-lo imutável.