quarta-feira, 9 de novembro de 2011

Erros Comuns (6): copy vs deepcopy: aliasing e mutabilidade

Está na altura de introduzir um erro comum, mas cuja origem é mais subtil.
Sabemos que um mesmo objecto pode ter associado diferentes nomes. Quando os objectos são imutáveis isso não coloca problemas. No exemplo abaixo o objecto 'abc' tem associado três nomes diferentes. Se alteramos algum deles o que acontece é que é criado um novo objecto que fica associado ao nome do objecto antigo. Lembre-se: as cadeias de caracteres são imutáveis!


01.>>> cadeia_1 = 'abc'
02.>>> cadeia_2 = 'abc'
03.>>> cadeia_3 = cadeia_1
04.>>> id(cadeia_1)
05.4357502896
06.>>> id(cadeia_2)
07.4357502896
08.>>> id(cadeia_3)
09.4357502896
10.>>> cadeia_2 = cadeia_2 + 'd'
11.>>> cadeia_3 = cadeia_3 + '3'
12.>>> cadeia_2
13.'abcd'
14.>>> cadeia_3
15.'abc3'
16.>>> cadeia_1
17.'abc'
18.>>> id(cadeia_2)
19.4361446864
20.>>> id(cadeia_3)
21.4361446720
22.>>> id(cadeia_1)
23.4357502896


Quando usamos objectos mutáveis o caso muda de figura.

01.>>> lista_1 = [1,2,3]
02.>>> lista_2 = [1,2,3]
03.>>> lista_3 = lista_2
04.>>> lista_1
05.[1, 2, 3]
06.>>> lista_2
07.[1, 2, 3]
08.>>> lista_3
09.[1, 2, 3]
10.>>> id(lista_1)
11.4515141032
12.>>> id(lista_2)
13.4515145344
14.>>> id(lista_3)
15.4515145344
16.>>> lista_1[1] = 'b'
17.>>> lista_1
18.[1, 'b', 3]
19.>>> lista_2
20.[1, 2, 3]
21.>>> lista_3
22.[1, 2, 3]
23.>>> lista_2[1] = 'X'
24.>>> lista_1
25.[1, 'b', 3]
26.>>> lista_2
27.[1, 'X', 3]
28.>>> lista_3
29.[1, 'X', 3]
30.>>>


Para melhor entender o que acontece vejamos a figura seguinte:




Como se pode ver a lista_2 e a lista_3 partilham a memória, e é isso que faz com que as modificações que afectam uma também afectem a outra. Um modo de resolver o problema consiste em fazer uma cópia do objecto em vez de partilharem a memória.

01.>>> lista_1 = [1,2,3]
02.>>> lista_2 = [1,2,3]
03.>>> lista_3 = lista_2[:] # <-- cópia!
04.>>> id(lista_1)
05.4515141536
06.>>> id(lista_2)
07.4515105464
08.>>> id(lista_3)
09.4515140816
10.>>> lista_3[1] = 'Y'
11.>>> lista_3
12.[1, 'Y', 3]
13.>>> lista_2
14.[1, 2, 3]
15.>>>


A figura abaixo ilustra a situação.





Parece que podemos ficar descansados com esta solução. Mas veja-se uma nova situação.

01.>>> lista_2 = [1,[2],3]
02.>>> lista_3 = lista_2[:] # <-- cópia?
03.>>> id(lista_2)
04.4515346624
05.>>> id(lista_3)
06.4515144696
07.>>> lista_3[1][0] = 'Z'
08.>>> lista_3
09.[1, ['Z'], 3]
10.>>> lista_2
11.[1, ['Z'], 3] # oops!
12.>>>


O que terá acontecido? Para entender vamos recorrer uma vez mais ao nosso diagrama de ligação dos nomes aos objectos. Como fica situação antes da alteração?





E depois da alteração da lista_3?






O método indicado de cópia apenas faz uma cópia de superfície, isto é, uma cópia ao primeiro nível. A única solução efectiva para esta questão é recorrer à função deepcopy do módulo copy.

01.>>> import copy
02.>>> lista_2 = [1,[2],3]
03.>>> lista_3 = copy.deepcopy(lista_2) #<-- cópia profunda!
04.>>> id(lista_2)
05.4515145344
06.>>> id(lista_3)
07.4515140816
08.>>> lista_3[1][0] = 'K'
09.>>> lista_3
10.[1, ['K'], 3]
11.>>> lista_2
12.[1, [2], 3] # <-- Sem problemas!
13.>>>


Vejamos graficamente o que acontece.





Agora nada é partilhado, a não ser os objectos primitivos (imutáveis). Por isso ao alteramos a ligação (profunda) na lista_3 nada se altera na lista_2.

Sem comentários:

Enviar um comentário