Tuplas: imutabilidade e quando usar

1. O que são tuplas e como criá-las

Tuplas são estruturas de dados sequenciais em Python que armazenam coleções ordenadas de elementos. Diferentemente das listas, tuplas são imutáveis — uma vez criadas, não podem ser alteradas. A sintaxe básica utiliza parênteses e vírgulas como delimitadores.

# Criando tuplas de diferentes formas
tupla_vazia = ()
tupla_com_um_elemento = (42,)  # vírgula obrigatória
tupla_com_varios_elementos = (1, 2, 3, 'Python', True)
tupla_sem_parenteses = 10, 20, 30  # tupla implícita
tupla_com_funcao = tuple([4, 5, 6])  # convertendo lista em tupla

print(type(tupla_sem_parenteses))  # <class 'tuple'>
print(tupla_sem_parenteses)        # (10, 20, 30)

A vírgula é o verdadeiro delimitador das tuplas. Os parênteses são opcionais na maioria dos casos, exceto para criar tuplas vazias ou evitar ambiguidades.

2. Imutabilidade: o coração das tuplas

A imutabilidade é a característica que define as tuplas. Enquanto listas podem ser modificadas livremente, tuplas oferecem garantia de que seu conteúdo não mudará.

# Comparação entre lista e tupla
lista_exemplo = [1, 2, 3]
tupla_exemplo = (1, 2, 3)

# Operações permitidas em listas, mas proibidas em tuplas
lista_exemplo[0] = 100  # OK
# tupla_exemplo[0] = 100  # TypeError: 'tuple' object does not support item assignment

lista_exemplo.append(4)  # OK
# tupla_exemplo.append(4)  # AttributeError: 'tuple' object has no attribute 'append'

del lista_exemplo[0]  # OK
# del tupla_exemplo[0]  # TypeError: 'tuple' object doesn't support item deletion

No entanto, cuidado com tuplas que contêm objetos mutáveis:

# Tupla com lista interna — falsa imutabilidade
tupla_mista = (1, [2, 3], 4)
tupla_mista[1].append(5)  # Isso funciona!
print(tupla_mista)  # (1, [2, 3, 5], 4) — a tupla em si não mudou, mas seu conteúdo interno sim

A tupla ainda contém os mesmos objetos, mas a lista interna foi alterada. A imutabilidade se aplica à referência, não ao conteúdo dos objetos mutáveis.

3. Operações permitidas com tuplas

Apesar da imutabilidade, muitas operações úteis são permitidas:

# Acesso por índice e fatiamento
tupla = (10, 20, 30, 40, 50)
print(tupla[0])      # 10
print(tupla[-1])     # 50
print(tupla[1:4])    # (20, 30, 40)

# Concatenação e repetição
tupla1 = (1, 2, 3)
tupla2 = (4, 5, 6)
print(tupla1 + tupla2)  # (1, 2, 3, 4, 5, 6)
print(tupla1 * 3)       # (1, 2, 3, 1, 2, 3, 1, 2, 3)

# Métodos úteis
numeros = (1, 2, 2, 3, 2, 4)
print(numeros.count(2))  # 3
print(numeros.index(3))  # 3

# Verificação de pertencimento
print(5 in tupla1)  # False
print(2 in tupla1)  # True

4. Desempacotamento de tuplas

O desempacotamento é uma das características mais elegantes das tuplas:

# Atribuição múltipla
coordenadas = (10, 20)
x, y = coordenadas
print(f"x={x}, y={y}")  # x=10, y=20

# Troca de valores (swap) sem variável temporária
a, b = 5, 10
a, b = b, a
print(a, b)  # 10 5

# Uso do operador * para capturar múltiplos itens
primeiro, *meio, ultimo = (1, 2, 3, 4, 5)
print(primeiro)  # 1
print(meio)      # [2, 3, 4]
print(ultimo)    # 5

# Desempacotamento em loops
nomes = ('Ana', 'Carlos', 'Maria')
for indice, nome in enumerate(nomes):
    print(f"{indice}: {nome}")

# Com zip()
idades = (25, 30, 28)
for nome, idade in zip(nomes, idades):
    print(f"{nome} tem {idade} anos")

5. Quando usar tuplas em vez de listas

Tuplas são ideais em situações específicas:

# 1. Dados que não devem ser alterados (constantes)
DIAS_SEMANA = ('Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo')
CONFIGURACAO_PADRAO = (800, 600, 24)  # resolução de tela

# 2. Chaves de dicionários (requisito de hashabilidade)
localizacoes = {
    (40.7128, -74.0060): 'Nova York',
    (34.0522, -118.2437): 'Los Angeles',
    (41.8781, -87.6298): 'Chicago'
}
print(localizacoes[(40.7128, -74.0060)])  # Nova York

# 3. Retorno múltiplo de funções
def dividir_e_resto(dividendo, divisor):
    quociente = dividendo // divisor
    resto = dividendo % divisor
    return quociente, resto  # retorna uma tupla

q, r = dividir_e_resto(17, 5)
print(f"Quociente: {q}, Resto: {r}")  # Quociente: 3, Resto: 2

# 4. Performance: tuplas são mais leves
import sys
lista_grande = list(range(1000))
tupla_grande = tuple(range(1000))
print(f"Lista: {sys.getsizeof(lista_grande)} bytes")  # ~8856 bytes
print(f"Tupla: {sys.getsizeof(tupla_grande)} bytes")  # ~8040 bytes

6. Tuplas nomeadas (namedtuple)

O módulo collections oferece uma extensão poderosa das tuplas:

from collections import namedtuple

# Criando uma namedtuple
Ponto = namedtuple('Ponto', ['x', 'y', 'z'])
ponto1 = Ponto(10, 20, 30)

# Acesso por nome (mais legível)
print(ponto1.x)  # 10
print(ponto1.y)  # 20

# Acesso por índice (ainda funciona)
print(ponto1[0])  # 10

# Comparação com dicionário
ponto_dict = {'x': 10, 'y': 20, 'z': 30}
print(ponto1.x == ponto_dict['x'])  # True — mas namedtuple é mais eficiente

# Vantagens: imutabilidade, desempenho, legibilidade
Pessoa = namedtuple('Pessoa', ['nome', 'idade', 'cidade'])
joao = Pessoa('João', 30, 'São Paulo')
print(f"{joao.nome}, {joao.idade} anos, mora em {joao.cidade}")

7. Comparação e ordenação de tuplas

Tuplas suportam comparação lexicográfica elemento a elemento:

# Comparação elemento a elemento
print((1, 2, 3) < (1, 2, 4))  # True — compara até encontrar diferença
print((1, 2, 3) < (1, 3, 0))  # True — 2 < 3
print((1, 2) < (1, 2, 3))    # True — tupla menor é considerada menor

# Ordenação de listas de tuplas
alunos = [
    ('Ana', 85),
    ('Carlos', 92),
    ('Beatriz', 78),
    ('Daniel', 85)
]

# Ordenar por nota (segundo elemento)
alunos_ordenados = sorted(alunos, key=lambda x: x[1])
print(alunos_ordenados)
# [('Beatriz', 78), ('Ana', 85), ('Daniel', 85), ('Carlos', 92)]

# Ordenação personalizada com tuplas como chave
def chave_ordenacao(aluno):
    return (-aluno[1], aluno[0])  # nota decrescente, nome alfabético

alunos_ordenados2 = sorted(alunos, key=chave_ordenacao)
print(alunos_ordenados2)
# [('Carlos', 92), ('Ana', 85), ('Daniel', 85), ('Beatriz', 78)]

8. Boas práticas e armadilhas comuns

# Tuplas como elementos de sets
set_tuplas = {(1, 2), (3, 4), (1, 2)}  # OK — tuplas são hasháveis
print(set_tuplas)  # {(1, 2), (3, 4)}

# Cuidado: tuplas com listas não podem ser hasháveis
# set_tuplas_mutaveis = {(1, [2, 3]), (4, 5)}  # TypeError: unhashable type: 'list'

# Quando converter entre tupla e lista
# Evite conversões desnecessárias — escolha a estrutura certa desde o início
dados = [1, 2, 3]
dados_tupla = tuple(dados)  # conversão para proteger dados
dados_lista = list(dados_tupla)  # conversão para modificar

# Boa prática: documente quando usar tuplas
# Ruim: usar tupla para dados que precisam ser modificados
# Bom: usar tupla para dados que representam registros imutáveis
def obter_configuracao():
    """Retorna configuração como tupla imutável."""
    return ('localhost', 8080, True, 'utf-8')  # host, porta, ssl, encoding

# Armadilha: esquecer a vírgula em tuplas de um elemento
nao_e_tupla = (42)  # Isso é um inteiro!
eh_tupla = (42,)    # Isso é uma tupla
print(type(nao_e_tupla))  # <class 'int'>
print(type(eh_tupla))     # <class 'tuple'>

Referências