Pular para o conteúdo

Fundamentos

Tuplas e desempacotamento em Python: valores que não mudam

Entenda as tuplas em Python: criar, acessar, por que não mudam, a vírgula de (5,), desempacotar com a, b = b, a e o asterisco, e usar em laços e na ESP32.

Por Prof. Rodrigo Faustino · Atualizado em · 13 min de leitura

Uma tupla é uma sequência ordenada de valores que não pode ser alterada depois de criada. Ela se escreve com parênteses e vírgulas, como (3, 4), e se lê quase como uma lista: tem índice, tem fatia, tem len. A diferença é que não existe append, nem troca de item, nem remove. Quem recebe uma tupla sabe que ela será a mesma até o fim, e é por isso que ela serve para grupos de valores que andam juntos, como as duas coordenadas de um ponto, o par de um pino com seu nome ou os dois valores que uma função devolve.

O artigo sobre return já usou a tupla sem apresentá-la: return q, r devolve uma. Aqui veremos como criar uma, a pegadinha do parêntese com um item só, por que ela não muda, o desempacotamento, que é o recurso mais útil que ela traz, o uso em laços e em listas de tuplas, e um exemplo com a ESP32.

Criar uma tupla

vazia = ()
ponto = (3, 4)
sem_parenteses = 1, 2, 3
unico = (5,)
nao_tupla = (5)
print(vazia, ponto, sem_parenteses)
print(unico, nao_tupla)
print(type(ponto), type(unico), type(nao_tupla))
print(tuple([1, 2, 3]))
print(tuple("abc"))
print(tuple(range(3)))
print(len(vazia), len(ponto), len(unico))
print("fim")

A saída é () (3, 4) (1, 2, 3), depois (5,) 5, depois <class 'tuple'> <class 'tuple'> <class 'int'>, e em seguida (1, 2, 3), ('a', 'b', 'c'), (0, 1, 2), 0 2 1 e fim.

O que cria a tupla é a vírgula, e não o parêntese. Por isso sem_parenteses = 1, 2, 3 já é uma tupla, e os parênteses só deixam a leitura mais clara. A consequência está nas duas linhas do meio. (5,) é uma tupla com um item, e (5) é só o número 5 entre parênteses, como em (2 + 3) * 4. Para uma tupla de um item só, a vírgula no fim é obrigatória.

Assim como a lista, a tupla se cria a partir de outra sequência, com tuple(...): de uma lista, de um texto ou de um range.

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

Acessar: igual à lista, com uma exceção

Índices, índices negativos, fatias, len, min, max, sum, in, index e count funcionam como nas listas, e o + e o * criam tuplas novas:

t = (12, 45, 7, 89, 23)
print(t[0], t[-1])
print(t[1:4])
print(tuple(reversed(t)))
print(len(t), min(t), max(t), sum(t))
print(7 in t, 8 in t)
print(t.index(7), t.count(45))
print((1, 2) + (3,))
print((0,) * 3)
print(t)
print("fim")

A saída é 12 23, (45, 7, 89), (23, 89, 7, 45, 12), 5 7 89 176, True False, 2 1, (1, 2, 3), (0, 0, 0), a tupla original e fim. A soma (1, 2) + (3,) precisa do (3,) com a vírgula, porque (1, 2) + 3 não é uma conta válida e dá TypeError.

Aqui há uma diferença entre o simulador e o Python de computador que vale a pena conhecer. A fatia com passo, como t[::-1] para inverter ou t[::2] para pegar de dois em dois, funciona numa tupla do Python de computador, mas o simulador, que imita o MicroPython, responde com um erro:

t = (12, 45, 7, 89, 23)
print(t[::-1])
NotImplementedError: only slices with step=1 (aka None) are supported

A fatia sem passo, como t[1:4], funciona normalmente. Para inverter, o caminho é tuple(reversed(t)), como na terceira linha do exemplo anterior, ou converter para lista, fatiar e voltar: tuple(list(t)[::-1]). Isso foi observado no simulador, e a regra para a placa deve ser conferida num programa curto antes de depender dela.

Por que a tupla não muda

Dois quadros lado a lado. No da lista, os valores 10, 20 e 30: lista de 1 igual a 25 troca o item do meio e lista ponto append de 40 acrescenta um item, tudo no lugar. No da tupla, os mesmos valores: t de 1 igual a 25 dá TypeError de atribuição de item, e para mudar é preciso converter com list, alterar e criar uma tupla nova com tuple. Uma faixa diz para usar tupla quando o grupo de valores não deve mudar.
A lista muda no lugar. A tupla recusa a troca, e o jeito de mudar é criar outra.

A tupla é imutável: depois de criada, nenhum item é trocado, acrescentado ou removido. A tentativa de trocar dá erro, como mostram os blocos de erro mais abaixo. O que se faz, quando é preciso uma versão diferente, é criar outra:

t = (10, 20, 30)
lista = list(t)
lista[1] = 25
lista.append(40)
t = tuple(lista)
print(t)
misto = ([1, 2], 3)
misto[0].append(99)
print(misto)
print("fim")

A saída é (10, 25, 30, 40), ([1, 2, 99], 3) e fim. Na primeira parte, a tupla virou lista, a lista foi alterada e uma nova tupla foi criada com o mesmo nome. A tupla antiga deixou de existir, e não foi alterada. Na segunda parte há uma pegadinha: a tupla misto guarda uma lista como primeiro item, e a lista, essa sim, aceita append. A tupla garante que o primeiro item sempre será aquela lista, e não garante que o conteúdo da lista fique parado. A imutabilidade vale para os itens da tupla, e quem estiver dentro deles continua com as regras do seu tipo.

Para que serve uma coisa que não muda? Para três coisas. A primeira é comunicar intenção: quem lê ponto = (3, 4) entende que são duas coordenadas fixas, e não uma lista que vai crescer. A segunda é segurança: uma função que recebe uma tupla não consegue estragar o que o chamador tem, o que os artigos sobre parâmetros e sobre listas mostraram ser um problema com listas. A terceira é servir de chave num dicionário, que é o tema do próximo artigo, porque só valores que não mudam podem ser chaves.

Desempacotar: dar um nome a cada item

Quatro quadros sobre desempacotamento. Em x, y igual a 3, 4, cada nome recebe um item na ordem e fica x igual a 3 e y igual a 4. Em a, b igual a b, a, os valores 1 e 2 trocam de lugar sem variável temporária. Em primeiro, asterisco meio, último, a lista 1 a 5 vira primeiro 1, meio como lista 2, 3, 4 e último 5. Em a, b igual a 1, 2, 3, há três valores para dois nomes e o resultado é ValueError.
Desempacotar é dar um nome a cada item. A conta de nomes e itens precisa fechar, ou ter um asterisco.

Desempacotar é atribuir os itens de uma sequência a vários nomes de uma vez. Com a tupla, é a forma mais natural de usar o que ela guarda:

x, y = (3, 4)
print(x, y)
a, b, c = 1, "dois", 3.5
print(a, b, c)
a = 1
b = 2
a, b = b, a
print(a, b)
p = 1
q = 2
p = q
q = p
print(p, q)
nome, idade = ["Ana", 20]
print(nome, idade)
primeiro, *meio, ultimo = [1, 2, 3, 4, 5]
print(primeiro, meio, ultimo)
cabeca, *cauda = (10, 20, 30)
print(cabeca, cauda)
_, valor = ("ignorar", 42)
print(valor)
print("fim")

A saída é 3 4, 1 dois 3.5, 2 1, 2 2, Ana 20, 1 [2, 3, 4] 5, 10 [20, 30], 42 e fim.

Os casos, na ordem em que aparecem:

  • x, y = (3, 4): cada nome recebe um item, na ordem. Os tipos podem ser diferentes, como na segunda linha.
  • a, b = b, a: a troca de duas variáveis, sem variável temporária. O lado direito é montado primeiro, como tupla (2, 1), e depois desempacotado em a e b. Em linguagens que não têm isso, a troca exige uma terceira variável.
  • p = q e depois q = p: o jeito errado de trocar. Depois da primeira linha, as duas valem 2, e o valor 1 se perdeu. A saída 2 2 é o sinal clássico.
  • Listas também desempacotam: nome, idade = ["Ana", 20] funciona do mesmo jeito. O que o desempacotamento exige é uma sequência, não necessariamente uma tupla.
  • O asterisco: primeiro, *meio, ultimo entrega o primeiro, o último, e uma lista com o que sobrou no meio. É útil quando não se sabe quantos itens há, e só interessam as pontas.
  • O sublinhado _: por convenção, nome de variável que não vai ser usada. A linha _, valor = ... diz "o primeiro item eu ignoro".

O * pode aparecer só uma vez na lista de nomes, e o item que ele recebe é sempre uma lista, mesmo que a original fosse uma tupla.

Receber o que uma função devolve

O mesmo mecanismo recebe os valores de uma função que devolve mais de um, como o artigo sobre return mostrou:

def dividir(a, b):
    return a // b, a % b

q, r = dividir(17, 5)
print(q, r)
res = dividir(17, 5)
print(res, type(res))
print(res[0], res[1])
pontos = [(0, 0), (3, 4), (-2, 5)]
for x, y in pontos:
    print("x", x, "y", y)
for i, (x, y) in enumerate(pontos):
    print(i, x, y)
print(max(pontos, key=lambda p: p[1]))
print(sorted(pontos, key=lambda p: p[0]))
print([(y, x) for x, y in pontos])
print("fim")

A saída é 3 2, (3, 2) <class 'tuple'>, 3 2, depois três linhas x 0 y 0, x 3 y 4 e x -2 y 5, depois 0 0 0, 1 3 4 e 2 -2 5, e então (-2, 5), [(-2, 5), (0, 0), (3, 4)], [(0, 0), (4, 3), (5, -2)] e fim.

A primeira parte mostra as duas maneiras de receber o que dividir devolve: abrir em q, r, ou guardar a tupla inteira e ler por índice. A segunda parte é uma lista de tuplas, o formato mais comum de dados pequenos e organizados em Python. Vamos olhar com calma.

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

Lista de tuplas e o laço for

A lista pontos com as tuplas 0 e 0, 3 e 4, e menos 2 e 5, percorrida por for x, y in pontos com print de x e y. Uma tabela mostra três voltas. Na primeira a tupla é 0, 0 e imprime 0 0. Na segunda é 3, 4 e imprime 3 4. Na terceira é menos 2, 5 e imprime menos 2 5. A cada volta a tupla do item é aberta nos nomes x e y.
No for, os nomes antes do in abrem cada tupla da lista, sem p[0] e p[1].

Uma lista de tuplas guarda registros simples: cada tupla é um registro, e a lista é a coleção. Pontos, pares de pino e nome, leituras com horário. O for x, y in pontos do exemplo acima desempacota cada tupla da lista nos nomes x e y, a cada volta, e o código fica bem mais legível do que o equivalente com índices, p[0] e p[1].

O enumerate, do artigo sobre listas, entrega pares de posição e item, e por isso aparece junto com o desempacotamento. Em for i, (x, y) in enumerate(pontos), os parênteses em volta de x, y dizem que o segundo item do par também é uma tupla a abrir.

As últimas linhas do exemplo mostram o parceiro de sempre: a key do artigo sobre lambda, map, filter e sorted. O max com key=lambda p: p[1] acha o ponto de maior y, e o sorted com key=lambda p: p[0] ordena pelo x. A compreensão de lista do artigo anterior também desempacota: [(y, x) for x, y in pontos] troca as coordenadas de cada ponto.

Comparar e usar como chave

Tuplas se comparam item a item, da esquerda para a direita, parando no primeiro que difere. Isso permite ordenar listas de tuplas sem nenhuma key, e é por esse motivo que a chave composta do artigo sobre sorted é uma tupla:

print((1, 2) < (1, 3))
print((2, 0) > (1, 9))
print((1, 2) == (1, 2))
print(sorted([(2, "b"), (1, "z"), (2, "a")]))
d = {(0, 0): "origem", (3, 4): "ponto"}
print(d[(3, 4)])
print("fim")

A saída é True, True, True, [(1, 'z'), (2, 'a'), (2, 'b')], ponto e fim. A primeira comparação olha o primeiro item, que é igual, e passa para o segundo. A segunda para no primeiro item, porque 2 é maior que 1, e ignora o 9. A ordenação coloca primeiro o 1, e entre os dois 2, decide pelo segundo item: "a" antes de "b".

A última parte antecipa o assunto do próximo artigo. Um dicionário guarda pares de chave e valor, e a tupla pode ser chave, o que permite usar coordenadas ou pares como "endereço" de um valor. Uma lista não pode: o erro está na seção a seguir.

Na ESP32: pares fixos e valores de volta

A configuração de um projeto muitas vezes é um conjunto fixo de pares, que não deve ser alterado durante a execução. A tupla de tuplas é um bom formato, e o laço com desempacotamento a percorre:

from machine import Pin
from time import sleep

LEDS = ((18, "verde"), (19, "amarelo"), (21, "vermelho"))
acesos = []
for pino, nome in LEDS:
    led = Pin(pino, Pin.OUT)
    led.on()
    sleep(0.1)
    led.off()
    acesos.append(nome)
print(acesos)

def ler_sensor():
    temperatura = 24
    umidade = 61
    return temperatura, umidade

temp, umid = ler_sensor()
print("temp", temp, "umid", umid)
print("fim")

A saída é ['verde', 'amarelo', 'vermelho'], temp 24 umid 61 e fim. O nome LEDS em maiúsculas é uma convenção para valores que não mudam, e a tupla reforça a ideia: nenhum trecho do programa vai trocar o pino de um LED por engano. O laço desempacota cada par em pino e nome, liga e desliga o LED daquele pino e guarda o nome. A função ler_sensor devolve temperatura e umidade de uma vez, e quem a chama abre o par em dois nomes. Na placa, os dois valores viriam de uma leitura de verdade, como a dos sensores do artigo sobre DHT22, HC-SR04 e PIR, e a função continuaria a ter a mesma forma.

Outro lugar em que a tupla aparece é a hora. A função time.localtime() devolve uma tupla com os componentes da data e da hora, e dá para abri-la em nomes:

import time
ano, mes, dia, hora, minuto, segundo, dia_semana, dia_ano = time.localtime()
print(type(ano), 1 <= mes <= 12)
print(len(time.localtime()))
print("fim")

A saída é <class 'int'> True, 8 e fim. No simulador, que imita o MicroPython, a tupla tem 8 itens. No Python de computador ela é um objeto parecido, com 9 itens, e por isso a mesma linha de desempacotamento daria erro lá. O número de nomes precisa bater com o número de itens, o assunto da próxima seção. Os valores de hora e data dependem do relógio da placa estar certo, e o programa não deve confiar neles sem acertar o relógio antes.

Erros comuns

As mensagens abaixo são as do simulador. O tipo do erro costuma ser o mesmo no Python de computador, e o texto pode mudar.

t = (1, 2, 3)
t[0] = 9
TypeError: 'tuple' object doesn't support item assignment

É o erro que a imutabilidade promete. A correção é criar outra tupla, como no exemplo de conversão, ou usar uma lista desde o início, se o grupo tem de mudar.

t = (1, 2, 3)
t.append(4)
AttributeError: 'tuple' object has no attribute 'append'

A tupla não tem os métodos que mudam o conteúdo: append, remove, sort e semelhantes dão o mesmo tipo de erro. O sorted(t) funciona, porque devolve uma lista nova.

a, b = (1, 2, 3)
ValueError: too many values to unpack (expected 2)

São três itens para dois nomes. Com poucos itens, como em a, b, c = (1, 2), o erro também é um ValueError, e a mensagem do simulador é need more than 2 values to unpack. A cura é contar os itens, ou usar o asterisco para guardar o que sobra.

t = (5)
print(len(t))
TypeError: object of type 'int' has no len()

É a pegadinha do parêntese. (5) é um número, e o len não serve para número. A tupla de um item é (5,).

t = (1, 2, 3)
print(t[3])
IndexError: tuple index out of range

Os índices de uma tupla de três itens vão de 0 a 2, exatamente como na lista.

d = {[1, 2]: "x"}
TypeError: unsupported type for __hash__: 'list'

Uma lista não pode ser chave de dicionário, porque pode mudar. A tupla (1, 2) pode. No Python de computador a mensagem diz unhashable type: 'list'.

O que costuma aparecer em sala

Estes padrões se repetem em turmas de qualquer nível.

O mais frequente é o (5) achando que é uma tupla de um item. O programa só falha adiante, quando alguém tenta percorrer ou medir o valor, e a vírgula esquecida fica a linhas de distância do erro.

O segundo é tentar mudar um item da tupla com t[0] = ..., ou chamar append, porque na cabeça é "uma lista com parênteses". A frase que ajuda é: o parêntese não muda o tipo, a vírgula e a imutabilidade sim.

O terceiro é a troca de variáveis do jeito errado, a = b seguida de b = a, que deixa as duas iguais. Quem aprende a, b = b, a tende a usar para tudo, e é o uso certo.

O quarto é o desempacotamento com a conta errada. Funciona com os dados do teste e quebra quando a função devolve outro número de itens. Em geral, o erro está na função que devolve, e a mensagem aponta para a linha de quem recebe.

O quinto é achar que uma tupla com uma lista dentro é totalmente protegida. A tupla guarda a lista, e a lista continua mudando por dentro, como o exemplo misto mostrou. Isso pega até quem programa há tempo.

O sexto é confundir lista de tuplas com tupla de listas, que parecem iguais no papel e se comportam de maneira oposta: na primeira, a coleção cresce e cada registro é fixo, e na segunda, o contrário.

Exercício: o ponto mais distante

Escreva um programa que pergunte quantos pontos serão lidos, leia o x e o y de cada um, guarde cada ponto como uma tupla, mostre a lista, o ponto mais distante da origem (some os valores absolutos de x e de y) e a lista com as coordenadas trocadas.

quantidade = int(input("quantos pontos: "))
pontos = []
for i in range(quantidade):
    x = int(input("x: "))
    y = int(input("y: "))
    pontos.append((x, y))
print("pontos", pontos)
longe = max(pontos, key=lambda p: abs(p[0]) + abs(p[1]))
print("mais longe", longe)
print("trocados", [(y, x) for x, y in pontos])

Com as entradas 3, 1, 2, -5, 4, 0 e -1, as três linhas finais são pontos [(1, 2), (-5, 4), (0, -1)], mais longe (-5, 4) e trocados [(2, 1), (4, -5), (-1, 0)]. O abs entrega o valor absoluto, e a soma dos dois valores absolutos é uma medida simples de distância, sem raiz quadrada. Para treinar, mostre também o ponto mais perto da origem e a quantidade de pontos em que x e y têm o mesmo sinal.

Perguntas frequentes

Qual a diferença entre lista e tupla, na prática? A lista é para coleções que crescem, encolhem ou mudam de ordem, e a tupla é para grupos de valores fixos e de papéis diferentes, como o par pino e nome. Na dúvida, se o grupo vai mudar, use lista.

A tupla é mais rápida ou ocupa menos memória? Não é por desempenho que se escolhe a tupla, e este artigo não mediu isso na ESP32. O motivo é a clareza e a segurança: quem lê sabe que o grupo não muda.

Posso ter uma tupla dentro de outra? Pode, e o desempacotamento aceita parênteses para abrir os níveis, como no for i, (x, y) in enumerate(pontos).

Para que serve o * no desempacotamento? Para receber o que sobrou, quando não se sabe quantos itens há ou só interessam algumas posições. O item com asterisco é sempre uma lista.

Existe tupla vazia útil? O () serve como valor inicial de algo que ainda não tem itens e não pode mudar, e como retorno de uma função que não achou nada. É raro no começo, e aparece com o tempo.

Para continuar

O desafio Maior de 2 pratica funções que devolvem valores, base do que foi visto sobre return e desempacotamento. Para rever as bases desta leitura, os artigos sobre return, listas e operações com listas completam este.

O próximo artigo trata dos dicionários: guardar valores por chave em vez de por posição, contar ocorrências e usar a tupla como chave, o que fecha o que ficou em aberto nesta seção.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows