Python Copiando Listas

A irmã da Ana vai ao mercado também e pede: "me passa uma cópia da sua lista, que eu tiro o que eu não quero e ponho as minhas coisas". A Ana escreve lista_irma = lista_ana, a irmã tira o café e põe chocolate… e o café some da lista da Ana também. Nesta aula você vai entender por que o = não copia uma lista, e os jeitos certos de fazer uma cópia de verdade.

Você vai aprender
  • O que b = a faz de verdade com listas: dois nomes para a mesma lista.
  • A diferença entre is ("é a mesma lista?") e == ("têm os mesmos itens?").
  • Os três jeitos de copiar: a.copy(), list(a) e a[:].
  • O limite da cópia: listas dentro de listas continuam compartilhadas (a cópia rasa).
  • Os erros reais: o copy sem parênteses e a grade [[0, 0]] * 3.

A ideia: duas etiquetas na mesma caixa

Até aqui, você pensou numa variável como uma caixa com etiqueta (aula Variáveis). Para listas, a imagem mais exata é outra: a lista é uma caixa, e a variável é uma etiqueta colada nela. Quando você escreve:

lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana

a primeira linha cria a caixa (a lista) e cola nela a etiqueta lista_ana. A segunda linha não cria caixa nenhuma: ela pega uma segunda etiqueta, lista_irma, e cola na mesma caixa. Existe uma lista só, com dois nomes. Tudo o que você fizer usando um nome, o outro vê, porque é a mesma lista.

É como a lista de compras compartilhada no celular: a Ana e a irmã abrem a mesma lista, e o que uma risca, some para as duas. Às vezes é isso que você quer. Quando não é, você precisa de uma cópia: uma caixa nova, com os mesmos itens.


1. b = a não copia

lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana
lista_irma.remove("café")
lista_irma.append("chocolate")
print("Ana: ", lista_ana)
print("Irmã:", lista_irma)
$ python3 igual.py
Ana:  ['arroz', 'feijão', 'chocolate']
Irmã: ['arroz', 'feijão', 'chocolate']

A irmã mexeu só na lista_irma, mas a lista da Ana perdeu o café e ganhou o chocolate. Nenhum erro, nenhum aviso: o Python fez exatamente o que foi pedido. O remove e o append mudaram a lista, e as duas etiquetas estão nela.

Como ver isso: is e ==

O operador is ("é", aula is e in) pergunta se dois nomes estão na mesma lista. O == pergunta se duas listas têm os mesmos itens, mesmo que sejam caixas diferentes:

>>> a = ["arroz", "feijão"]
>>> b = a
>>> b is a
True
>>> c = a.copy()
>>> c is a
False
>>> c == a
True
>>> b.append("sal")
>>> a
['arroz', 'feijão', 'sal']
>>> c
['arroz', 'feijão']
  • b is a é True: b e a são a mesma lista.
  • c is a é False: o copy() criou uma lista nova. Mas c == a é True: por enquanto, as duas têm os mesmos itens.
  • O append feito por b apareceu em a (são a mesma), mas não em c (é outra).
E se eu der um valor novo para b?

Mudar a lista (com append, remove, b[0] = …) é diferente de dar outra lista para a variável. Um = com uma lista nova descola a etiqueta e a cola numa caixa nova; a lista antiga não muda:

>>> a = ["arroz", "feijão"]
>>> b = a
>>> b = ["pão"]
>>> a
['arroz', 'feijão']
>>> b
['pão']

É por isso que, com números e textos, b = a nunca causa essa surpresa: eles são imutáveis, então não há como "mudar a caixa"; toda mudança é um = com um valor novo. A aula Mutável × Imutável conta essa história inteira.

Missão: conserte a segunda linha para a irmã receber uma cópia da lista. A lista da Ana tem de continuar com o café.

lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana
lista_irma.remove("café")
lista_irma.append("chocolate")
print("Ana: ", lista_ana)
print("Irmã:", lista_irma)
lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana.copy()
lista_irma.remove("café")
lista_irma.append("chocolate")
print("Ana: ", lista_ana)
print("Irmã:", lista_irma)

2. Copiando de verdade: copy()

lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana.copy()
lista_irma.remove("café")
lista_irma.append("chocolate")
print("Ana: ", lista_ana)
print("Irmã:", lista_irma)
$ python3 copia.py
Ana:  ['arroz', 'feijão', 'café']
Irmã: ['arroz', 'feijão', 'chocolate']
lista_ana.copy()
O método copy ("copiar") cria uma lista nova, com os mesmos itens, na mesma ordem, e a devolve. Os parênteses são obrigatórios: é uma chamada.
lista_irma = …
A lista nova ganha a etiqueta lista_irma. Agora são duas caixas, cada uma com o seu nome.

Agora cada uma mexe na sua. A Ana continua com o café; a irmã tem o chocolate.


3. Três jeitos de copiar

Além do copy(), há outros dois jeitos muito comuns, e você vai encontrar os três em programas dos outros:

original = ["arroz", "feijão", "café"]
copia1 = original.copy()
copia2 = list(original)
copia3 = original[:]
original.append("leite")
print("original:", original)
print("copia1:  ", copia1)
print("copia2:  ", copia2)
print("copia3:  ", copia3)
$ python3 tres_jeitos.py
original: ['arroz', 'feijão', 'café', 'leite']
copia1:   ['arroz', 'feijão', 'café']
copia2:   ['arroz', 'feijão', 'café']
copia3:   ['arroz', 'feijão', 'café']

O leite entrou só na original: as três cópias são listas independentes.

JeitoComo funcionaQuando usar
b = a.copy()O método de cópia da listaO mais claro: quem lê sabe na hora que é uma cópia. Prefira este
b = list(a)A função list() (aula Listas) cria uma lista nova com os itens de aQuando a pode não ser uma lista (uma tupla, um range) e você quer uma lista
b = a[:]Uma fatia do começo ao fim (aula Acessando Itens) é sempre uma lista novaMuito comum em código antigo; curto, mas menos óbvio para quem está começando
b = aNão copia: outro nome para a mesma listaSó quando você quer mesmo que os dois nomes vejam as mesmas mudanças

Você já usou um desses sem perceber: na aula Removendo Itens, o for nota in notas[:] percorria uma cópia enquanto o remove mexia na original.


4. O limite: listas dentro de listas

Os três jeitos fazem uma cópia rasa (em inglês, shallow copy): a caixa de fora é nova, mas o que está dentro dela não é copiado. Se um item for uma lista, as duas caixas guardam a mesma lista de dentro. Veja com a turma, em que cada aluno é um par [nome, nota]:

turma = [["Ana", 9.5], ["Bruno", 7.0]]
copia = turma.copy()
copia.append(["Carla", 8.0])
copia[0][1] = 10.0
print("turma:", turma)
print("copia:", copia)
$ python3 rasa.py
turma: [['Ana', 10.0], ['Bruno', 7.0]]
copia: [['Ana', 10.0], ['Bruno', 7.0], ['Carla', 8.0]]
  • A Carla entrou só na copia: o append mexeu na caixa de fora, que é nova. Até aqui, a cópia funcionou.
  • Mas a nota da Ana virou 10.0 nas duas. copia[0] é o par da Ana, e esse par é o mesmo nas duas listas: a cópia rasa copiou a etiqueta do par, e não o par.

Para listas de textos e números, como a lista de compras, a cópia rasa é tudo de que você precisa. Para listas de listas, existe a cópia profunda, que copia também o que está dentro. Ela tem aula própria: Cópia Rasa e Cópia Profunda.


Quando dá errado

"Mexi numa lista e a outra mudou"

O sintoma do igual.py: nenhum erro, mas uma lista que você não tocou mudou sozinha. A causa é sempre a mesma: em algum lugar, um b = a (ou um a passado para algum lugar e guardado com outro nome) fez os dois nomes apontarem para a mesma lista. Como descobrir: print(b is a); se der True, é a mesma lista. Como consertar: b = a.copy() no lugar onde a "cópia" foi feita.

O copy sem parênteses
lista_ana = ["arroz", "feijão", "café"]
lista_irma = lista_ana.copy
print(lista_irma)
lista_irma.append("chocolate")
$ python3 sem_parenteses.py
<built-in method copy of list object at 0x7fa8813b5d40>
Traceback (most recent call last):
  File "/home/ana/sem_parenteses.py", line 4, in <module>
    lista_irma.append("chocolate")
    ^^^^^^^^^^^^^^^^^
AttributeError: 'builtin_function_or_method' object has no attribute 'append'

Linha por linha:

  • Linha 2: sem os parênteses, o copy não foi chamado. A variável lista_irma recebeu o próprio método, a "ferramenta de copiar", e não uma lista.
  • Linha 3: o print mostra isso: <built-in method copy of list object at 0x…>, "método embutido copy de um objeto lista". O número depois do at é um endereço da memória e muda a cada execução.
  • Linha 4: AttributeError: 'builtin_function_or_method' object has no attribute 'append': um método não tem append, porque não é uma lista.

Como consertar: lista_ana.copy(), com os parênteses.

A grade [[0, 0]] * 3: três vezes a mesma linha

A professora quer uma grade de notas com 3 alunos e 2 provas, começando tudo em zero. Ela lembrou do * da aula Adicionando Itens:

grade = [[0, 0]] * 3
print(grade)
grade[0][0] = 5
print(grade)
$ python3 grade.py
[[0, 0], [0, 0], [0, 0]]
[[5, 0], [5, 0], [5, 0]]

Ela mudou a primeira prova do primeiro aluno, e mudou a dos três. O * 3 não copiou a lista [0, 0] três vezes: ele pôs a mesma lista três vezes na grade, como três etiquetas na mesma caixa. (Com [0] * 3 não há problema, porque o 0 é um número, imutável.)

Como consertar: criar uma lista nova para cada linha. O jeito curto é uma list comprehension (aula List Comprehension), que roda [0, 0] de novo a cada volta:

>>> grade = [[0, 0] for i in range(3)]
>>> grade[0][0] = 5
>>> grade
[[5, 0], [0, 0], [0, 0]]
A cópia que "não copiou" o que está dentro

Você copiou com copy(), mudou um item de dentro de um item (copia[0][1] = …) e a original mudou também: é o limite da cópia rasa, da seção 4. Como consertar: veja a cópia profunda, em Cópia Rasa e Cópia Profunda.


Referência rápida

EscritaO que fazLista nova?
b = aMais um nome para a mesma listaNão
b = a.copy()Cópia rasa (o jeito mais claro)Sim
b = list(a)Cópia rasa; aceita qualquer sequênciaSim
b = a[:]Cópia rasa por fatiaSim
b is aTrue se são a mesma lista—
b == aTrue se têm os mesmos itens, na mesma ordem—
[[0, 0] for i in range(3)]Uma lista de listas, cada uma novaSim
SintomaCausa provável
Mexi numa lista e outra mudou juntob = a em vez de b = a.copy()
<built-in method copy of list object at 0x…>a.copy sem parênteses
AttributeError: 'builtin_function_or_method' object has no attribute 'append'A variável guarda o método, não a lista (faltaram os parênteses)
Mudei uma linha da grade e todas mudaram[[…]] * n: use [[…] for i in range(n)]
Mudei um item de dentro na cópia e a original mudouCópia rasa: veja Cópia Rasa e Cópia Profunda

Exercícios

1. Uma cópia de verdade

Complete com o método que copia a lista:

reserva = compras.

2. Cópia por fatia

Complete a fatia que copia a lista inteira:

reserva = compras[]

3. A mesma lista?

Depois de a = [1, 2] e b = a, o que b is a responde?

4. Depois de a = [1, 2], b = a e b.append(3), quanto vale a?

5. Qual destas linhas não cria uma lista nova?

6. Com c = a.copy(), o que c == a e c is a respondem logo depois da cópia?

Missão final: a lista do mês

Missão: a Ana quer montar a lista do mês a partir da lista da semana, acrescentando sabão e detergente, mas a lista da semana está sendo estragada. Faça mes ser uma cópia de semana.

semana = ["arroz", "feijão", "café"]
mes = semana
mes.extend(["sabão", "detergente"])
print("Semana:", semana)
print("Mês:", mes)
semana = ["arroz", "feijão", "café"]
mes = semana.copy()
mes.extend(["sabão", "detergente"])
print("Semana:", semana)
print("Mês:", mes)

Outro jeito, sem cópia nenhuma: mes = semana + ["sabão", "detergente"]. O + já cria uma lista nova (aula Adicionando Itens).


Resumo

  • Com listas, b = a não copia: cria outro nome para a mesma lista. O que um muda, o outro vê.
  • is pergunta se é a mesma lista; == pergunta se os itens são iguais.
  • Para copiar: a.copy() (o mais claro), list(a) ou a[:].
  • Dar uma lista nova para a variável (b = [...]) descola o nome; não muda a lista antiga.
  • As três cópias são rasas: listas dentro da lista continuam compartilhadas. Para isso existe a cópia profunda (Cópia Rasa e Cópia Profunda).
  • [[0, 0]] * 3 repete a mesma lista três vezes; use [[0, 0] for i in range(3)].