Tuplos e listas

Sequências, desempacotamento, operações, mutação, alias e cópia superficial.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

As notas [12, 15] formam uma lista, uma sequência onde podemos acrescentar 18 ou substituir 12 por 20, enquanto o tuplo (12, 15) conserva os valores por ordem sem permitir alterar as suas posições; essa diferença chama-se mutabilidade.

Sequências e tuplos

Strings, listas e tuplos suportam len, índices, fatias e pertença com in. Uma lista escreve-se [12, 15]; um tuplo pode escrever-se (12, 15). A vírgula é que cria o tuplo: (12,) tem um elemento, mas (12) é apenas o inteiro 12 entre parênteses.

O desempacotamento associa elementos a nomes: x, y = (3, 4). Nesta forma, o número de nomes tem de coincidir com o número de elementos. a, b = b, a troca os valores porque o lado direito é calculado antes das atribuições.

Um tuplo é imutável, mas pode conter objetos mutáveis. Em registo = ("Ana", [12, 15]), não podes substituir registo[0], mas podes alterar a lista registo[1]. Imutabilidade do recipiente não significa imutabilidade de todos os objetos alcançáveis a partir dele.

Alterar uma lista

Segue uma lista concreta desde o início:

xs = [10, 20]
xs.append(30)
xs[0] = 11
print(xs)

A lista passa por [10, 20], depois [10, 20, 30] e depois [11, 20, 30]. append altera a própria lista; a atribuição por índice substitui um elemento existente.

OperaçãoEfeito
xs.append(x)Acrescenta um elemento, mesmo que x seja uma lista
xs.extend(ys)Acrescenta cada elemento de ys
xs.insert(i, x)Insere antes da posição i
xs.pop(i)Retira e devolve o elemento dessa posição
xs.remove(x)Retira a primeira ocorrência de x; falha se faltar
del xs[i]Retira o elemento sem o devolver
xs[a:b] = ysSubstitui uma fatia, podendo mudar o comprimento

append, extend, insert, remove, sort e reverse devolvem None. Não escrevas xs = xs.append(4), porque acabarias com xs associado a None.

Por exemplo, ys = xs.append(4) guarda None em ys e altera xs. xs + ys cria outra lista. Não alteres o comprimento da lista que estás a percorrer sem uma estratégia explícita; retirar um elemento pode deslocar o seguinte e fazê-lo escapar ao ciclo.

Alias contra cópia, igualdade contra identidade

b = a não copia a lista. Cria outro nome para o mesmo objeto, um alias. a == b compara conteúdos; a is b testa se os nomes referem o mesmo objeto.

Comparar alias e cópia
notas = [12, 15]
alias = notas
copia = notas.copy()
alias.append(18)
copia[0] = 20
print(notas)
print(copia)
print(alias is notas, copia is notas)
print([1, 2] == [1, 2])
Resultado

Executa o código para ver a saída e os gráficos.

append altera a lista partilhada por notas e alias, enquanto a alteração em copia atinge outra lista. Dois objetos distintos podem ter conteúdo igual.

A identidade ajuda a seguir a partilha, mas um cálculo que só depende dos valores não deve depender dela.

notasaliascopia[12, 15, 18][20, 15]

Listas de listas e cópia superficial

a.copy() e a[:] criam uma cópia superficial. Copiam o recipiente, mantendo referências aos elementos. Se os elementos forem listas, continuam partilhados.

Cópia superficial de uma matriz
matriz = [[1, 2], [3, 4]]
copia = matriz.copy()
copia[0][0] = 9
print(matriz)
print(copia)
Resultado

Executa o código para ver a saída e os gráficos.

As duas primeiras saídas contêm o 9 porque a primeira linha é partilhada. Distingue as duas operações sobre a cópia: copia[0] = [9, 9] substituiria a referência guardada na cópia e deixaria a matriz intacta, enquanto copia[0][0] = 9 altera a linha partilhada e aparece nas duas listas. Para copiar cada linha sem usar ainda compreensões, constrói a lista nova com um ciclo:

independente = []
for linha in matriz:
    independente.append(linha.copy())

Cada iteração acrescenta a cópia de uma linha. Se executarmos depois independente[0][0] = 7, a matriz original mantém [[9, 2], [3, 4]] e a nova fica [[7, 2], [3, 4]]. Os elementos das linhas são inteiros, por isso a alteração já é independente. Para estruturas mais profundas, copiar um só nível adicional pode não chegar. A forma equivalente com compreensão será explicada em compreensões e geradores.

matrizcopialista exterior Alista exterior B0101[9, 2][3, 4]Caixas: objetos. Setas: referências.

O esquema mostra o estado após copia[0][0] = 9: cada caixa é um objeto e cada seta é uma referência a esse objeto. As listas exteriores são distintas, mas as referências nas posições 0 e 1 de ambas chegam às mesmas listas interiores.

Por isso, alterar um elemento da primeira linha através de copia também muda o que vemos através de matriz.

Evita [[0] * 3] * 2 para criar duas linhas independentes. A repetição exterior repete a referência à mesma linha. Quando precisares de uma linha nova em cada posição, cria cada linha dentro de um ciclo; a versão com compreensão aparece na lição própria.

Ver a mesma construção com um ciclo
matriz = []
for i in range(2):
    matriz.append([0] * 3)
matriz[0][0] = 5
print(matriz)

O resultado é [[5, 0, 0], [0, 0, 0]], porque criar a linha dentro do ciclo impede a partilha.

Percorrer e combinar sequências

enumerate(xs) produz pares com índice e valor. zip(xs, ys) combina elementos na mesma posição e, por defeito, termina na sequência mais curta. Se o contrato exige comprimentos iguais, verifica-o antes de combinar.

O produto interno de dois vetores com o mesmo comprimento é a soma dos produtos correspondentes. Para [2, 3] e [4, 5], calculamos 2×4+3×5=232\times4+3\times5=23. Para dois vetores vazios, a soma é zero.

def produto_interno(u, v):
    if len(u) != len(v):
        raise ValueError("comprimentos diferentes")
    soma = 0
    for a, b in zip(u, v):
        soma += a * b
    return soma

print(produto_interno([2, 3], [4, 5]))

O resultado é 23; raise comunica uma entrada inválida e o tratamento dessa exceção está explicado na última lição do percurso.

Ordenar sem confundir os resultados

sorted(xs) devolve uma lista nova e deixa xs intacta. xs.sort() ordena a lista existente e devolve None. reverse=True pede ordem decrescente. Para ordenar registos por um campo, usa key; a lição de programação funcional explica como passar essa função.

Exercícios

Seguir o efeito e o retorno de um método

Prevê os conteúdos de xs e retorno depois destas instruções. Depois explica o que muda se substituíres a primeira chamada por xs = xs.append([2, 3]), incluindo o efeito na instrução seguinte.

xs = [1]
retorno = xs.append([2, 3])
xs.extend([4, 5])

Solução

append acrescenta a lista [2, 3] como um único elemento, pelo que xs passa a [1, [2, 3]]. O retorno do método é None, guardado em retorno. extend acrescenta separadamente 4 e 5, produzindo [1, [2, 3], 4, 5].

Na substituição, a chamada ainda altera a lista, mas a atribuição associa xs ao retorno None. A instrução xs.extend([4, 5]) lança então AttributeError, porque None não tem esse método. Para conservar a lista, chama o método sem atribuir o seu retorno a xs.

Confere a tua resposta:

  • Obtive [1, [2, 3], 4, 5] e None.
  • Distingui append de extend.
  • Expliquei a perda do nome da lista na atribuição incorreta.
Voltar à explicação
Combinar vetores com o mesmo comprimento

Define produto_interno(u, v): recebe duas listas de números e devolve a soma dos produtos dos elementos na mesma posição, sem alterar as entradas. A soma de dois vetores vazios é zero. Se os comprimentos forem diferentes, lança ValueError. Usa zip depois de validar a condição. Para [2, 3] e [4, 5], o resultado é 2×4+3×5=232\times4+3\times5=23.

Python
def produto_interno(u, v):
    return 0
Testes e resultado

Verifica o código para comparar o resultado com os testes.

Testes
  1. Produtos correspondentes
    Código do teste
    print(produto_interno([2, 3], [4, 5]))
    Esperado:
    23
  2. Sinais e zero
    Código do teste
    print(produto_interno([-2, 0, 3], [4, 7, -1]))
    Esperado:
    -11
  3. Vetores vazios
    Código do teste
    print(produto_interno([], []))
    Esperado:
    0
  4. Comprimentos diferentes
    Código do teste
    try:
        produto_interno([1, 2], [3])
    except ValueError:
        print("comprimentos diferentes")
    else:
        print("erro não detetado")
    Esperado:
    comprimentos diferentes

Pista
Sem validação, zip termina no vetor mais curto e oculta dados em falta.
Solução

Primeiro comparamos os comprimentos. Só depois percorremos os pares e acumulamos cada produto. O total inicial zero também resolve a entrada vazia.

def produto_interno(u, v):
    if len(u) != len(v):
        raise ValueError("comprimentos diferentes")
    total = 0
    for a, b in zip(u, v):
        total += a * b
    return total

Para [-2, 0, 3] e [4, 7, -1], os produtos são -8, 0 e -3, cuja soma é -11. Não somamos todos os pares possíveis: cada posição só combina com a posição correspondente. O tratamento de raise é desenvolvido na lição de exceções.

Voltar à explicação
Comparar dois nomes

a = [1, 2], b = a, c = a.copy(). Depois de b[0] = 9, prevê a, c, a is b e a == c.

Pista
b partilha o recipiente com a.
Mais uma pista
c tem outro recipiente com os valores iniciais.
Solução

a vale [9, 2], c vale [1, 2], a is b é True e a == c é False. A mudança feita por b afeta a lista de a, sem mudar c.

Confere a tua resposta:

  • Previ [9, 2] para a.
  • Previ [1, 2] para c.
  • Distingui True na identidade e False na igualdade.

Erros frequentes

Confundir igualdade dos conteúdos com identidade.
Voltar à explicação
Evitar linhas partilhadas

Corrige zeros(linhas, colunas) para devolver uma matriz de zeros com linhas independentes. As dimensões são inteiros não negativos; não imprimas.

Python
def zeros(linhas, colunas):
    return [[0] * colunas] * linhas
Testes e resultado

Verifica o código para comparar o resultado com os testes.

Testes
  1. Alterar só a primeira linha
    Código do teste
    m = zeros(3, 2)
    m[0][0] = 5
    print(m)
    Esperado:
    [[5, 0], [0, 0], [0, 0]]
  2. Sem linhas
    Código do teste
    print(zeros(0, 3))
    Esperado:
    []
  3. Sem colunas
    Código do teste
    print(zeros(2, 0))
    Esperado:
    [[], []]
  4. Linhas vazias independentes
    Código do teste
    m = zeros(2, 0)
    m[0].append(5)
    print(m)
    Esperado:
    [[5], []]
  5. Uma célula
    Código do teste
    print(zeros(1, 1))
    Esperado:
    [[0]]

Pista

A repetição exterior repete referências à mesma linha, não a copia.

Mais uma pista

Cria uma lista nova em cada iteração, mesmo quando há zero colunas.

Solução
def zeros(linhas, colunas):
    matriz = []
    for i in range(linhas):
        matriz.append([0] * colunas)
    return matriz

Cada iteração cria outra linha. Com zero linhas, obténs []; com duas linhas e zero colunas, obténs duas listas vazias distintas. Alterar uma delas não afeta a outra.

Erros frequentes

Repetir a mesma lista interior, copiar só a lista exterior ou usar print em vez de return.

Voltar à explicação
Verificar um contrato

list(zip([1, 2, 3], [4, 5])) produz o quê?

A tua resposta

Pista

zip termina na sequência mais curta por defeito.

Mais uma pista
Há dois pares completos.
Solução

Produz [(1, 4), (2, 5)]. O elemento 3 não tem correspondente. Para um contrato de comprimentos iguais, verifica-os explicitamente.

  • [(1, 4), (2, 5)]. Só existem dois pares completos.
  • [(1, 4), (2, 5), (3, None)]. zip não preenche a sequência menor com None.
  • ValueError. Sem strict=True, não rejeita estes comprimentos.

Erros frequentes

Assumir que zip rejeita sempre comprimentos diferentes.

Voltar à explicação
Guardar uma ordenação

xs = [3, 1, 2]. Se executares ys = xs.sort(), quanto vale ys?

A tua resposta

Pista
O método altera xs.
Mais uma pista
O seu retorno não é a lista ordenada.
Solução

ys vale None e xs passa a [1, 2, 3]. Para receber outra lista, usa ys = sorted(xs).

  • None. sort altera a lista e devolve None.
  • [1, 2, 3]. Este é o novo conteúdo de xs, não o retorno.
  • [3, 1, 2]. A entrada foi alterada.

Erros frequentes

Atribuir o retorno de uma operação mutadora como se fosse a coleção.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.