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.
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
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 supportedA 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
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
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 emaeb. Em linguagens que não têm isso, a troca exige uma terceira variável.p = qe depoisq = p: o jeito errado de trocar. Depois da primeira linha, as duas valem 2, e o valor 1 se perdeu. A saída2 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, ultimoentrega 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.
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
Versão Community grátis · Windows
Lista de tuplas e o laço for
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] = 9TypeError: '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 rangeOs í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.