Entendendo dict.copy () - superficial ou profundo?

429

Ao ler a documentação dict.copy(), ele diz que faz uma cópia superficial do dicionário. O mesmo vale para o livro que estou seguindo (Beazley's Python Reference), que diz:

O método m.copy () faz uma cópia superficial dos itens contidos em um objeto de mapeamento e os coloca em um novo objeto de mapeamento.

Considere isto:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

Portanto, presumi que isso atualizaria o valor de original(e adicionaria 'c': 3) também porque eu estava fazendo uma cópia superficial. Como se você fizer isso para uma lista:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Isso funciona conforme o esperado.

Como as duas cópias são rasas, por que dict.copy()isso não funciona como eu esperava? Ou meu entendimento de cópia superficial e profunda é falha?

user225312
fonte
2
Pitoresco que eles não explicam "superficial". Conhecimento interno, piscadela. Apenas o comando e as chaves são uma cópia, enquanto os comandos aninhados dentro desse primeiro nível são referências, não podem ser excluídos em um loop, por exemplo. Portanto, o dict.copy () do Python nesse caso não é útil nem intuitivo. Obrigado pela sua pergunta.
gseattle

Respostas:

991

Por "cópia superficial", significa que o conteúdo do dicionário não é copiado por valor, mas apenas criando uma nova referência.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

Por outro lado, uma cópia profunda copiará todo o conteúdo por valor.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Assim:

  1. b = a: Atribuição de referência, marca ae baponta para o mesmo objeto.

    Ilustração de 'a = b': 'a' e 'b' apontam para '{1: L}', 'L' aponta para '[1, 2, 3]'.

  2. b = a.copy(): Cópia rasa ae bse tornará dois objetos isolados, mas seu conteúdo ainda compartilha a mesma referência

    Ilustração de 'b = a.copy ()': 'a' aponta para '{1: L}', 'b' aponta para '{1: M}', 'L' e 'M' apontam para '[ 1, 2, 3] '.

  3. b = copy.deepcopy(a): Cópia profunda, ae ba estrutura e o conteúdo ficam completamente isolados.

    Ilustração de 'b = copy.deepcopy (a)': 'a' aponta para '{1: L}', 'L' aponta para '[1, 2, 3]';  'b' aponta para '{1: M}', 'M' aponta para uma instância diferente de '[1, 2, 3]'.

kennytm
fonte
Boa resposta, mas você pode considerar corrigir o erro gramatical em sua primeira frase. E não há razão para não usar Lnovamente b. Fazer isso simplificaria o exemplo.
Tom Russell
@kennytm: Qual é a diferença entre os dois primeiros exemplos? Você chega ao mesmo resultado, mas com uma implementação interna ligeiramente diferente, mas para que isso importa?
JavaSa 20/01/19
@ TomRussell: Ou qualquer um, uma vez que essa pergunta é bastante antiga, minha pergunta de esclarecimento é para todos
JavaSa
@JavaSa Importa se, digamos, você faz b[1][0] = 5. Se bfor uma cópia superficial, você acabou de mudar a[1][0].
Tom Russell
2
Ótima explicação, ... realmente salvou o meu dia! Obrigado ... Isso pode ser aplicado à lista, str e outros tipos de dados do python?
Bhuro
38

Não se trata de cópia profunda ou superficial, nada do que você está fazendo é cópia profunda.

Aqui:

>>> new = original 

você está criando uma nova referência à lista / ditado referenciado pelo original.

enquanto aqui:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

você está criando uma nova lista / ditado que é preenchido com uma cópia das referências de objetos contidas no contêiner original.

Lie Ryan
fonte
31

Veja este exemplo:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Agora vamos alterar um valor no nível 'raso' (primeiro):

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Agora vamos alterar um valor um nível mais profundo:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed
eumiro
fonte
8
no change in original, since ['a'] is an immutable integerEste. Na verdade, responde à pergunta.
precisa saber é o seguinte
8

Adicionando à resposta do kennytm. Quando você faz uma cópia superficial parent.copy () um novo dicionário é criado com as mesmas chaves, mas os valores não são copiados são referenced.If você adicionar um novo valor para parent_copy ele não vai efeito pai porque parent_copy é um novo dicionário sem referência.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

O valor de hash (id) do pai [1] , parent_copy [1] é idêntico, o que implica [1,2,3] do pai [1] e do parent_copy [1] armazenados no id 140690938288400.

Mas o hash do pai e o parent_copy são diferentes, o que implica que eles são dicionários diferentes e parent_copy é um novo dicionário que tem valores de referência aos valores do pai

Vkreddy Komatireddy
fonte
5

"novo" e "original" são ditados diferentes, é por isso que você pode atualizar apenas um deles. Os itens são copiados superficialmente, não o ditado em si.

Joril
fonte
2

O conteúdo é copiado superficialmente.

Portanto, se o original dictcontiver um listou outro dictionary, modificá-los no original ou em sua cópia superficial os modificará (o listou o dict) no outro.

Caçador da selva
fonte
1

Na sua segunda parte, você deve usar new = original.copy()

.copye =são coisas diferentes.

朱骏 杰
fonte