Como copiar um dicionário e editar apenas a cópia

856

Alguém pode explicar isso para mim? Isso não faz nenhum sentido para mim.

Copio um dicionário para outro e edito o segundo e ambos são alterados. Por que isso está acontecendo?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}
MadSc13ntist
fonte
4
O PythonTutor é ótimo para visualizar referências do Python. Aqui está esse código na última etapa . Você pode ver dict1e dict2apontar para o mesmo ditado.
precisa saber é

Respostas:

883

Python nunca copia implicitamente objetos. Quando você definedict2 = dict1 , está fazendo com que eles se refiram ao mesmo objeto de ditado exato; portanto, quando você o modifica, todas as referências a ele continuam se referindo ao objeto em seu estado atual.

Se você deseja copiar o ditado (o que é raro), é necessário fazê-lo explicitamente com

dict2 = dict(dict1)

ou

dict2 = dict1.copy()
Mike Graham
fonte
26
Talvez seja melhor dizer "dict2 e dict1 apontam para o mesmo dicionário", você não está alterando dict1 ou dict2, mas para o que eles apontam.
precisa saber é o seguinte
276
Observe também que o dict.copy () é superficial, se houver uma lista aninhada / etc, as alterações serão aplicadas a ambos. IIRC. A cópia em profundidade evitará isso.
Will
16
Não é correto que o python nunca copie implicitamente objetos. Tipos de dados primitivos, como int, float e bool, também são tratados como objetos (basta fazer um dir(1)para ver isso), mas são copiados implicitamente.
Daniel kullmann
17
@danielkullmann, acho que você pode ter mal-entendidos sobre Python com base em como outras linguagens com as quais você lidou funcionam. Em Python, a) Não há conceito de "tipos de dados primitivos". int, float, E boolcasos são reais Python objetos, e b) objetos desses tipos não são copiados implicitamente quando você passá-los, e não em um nível Python semântica com certeza e nem mesmo como um detalhe de implementação em CPython.
Mike Graham
39
Retóricas sem fundamento, como "cópia profunda é considerada prejudicial", são inúteis. Como tudo é igual, a cópia superficial de uma estrutura de dados complexa tem uma probabilidade significativamente maior de gerar problemas inesperados de casos extremos do que a cópia profunda da mesma estrutura. Uma cópia na qual as modificações modificam o objeto original não é uma cópia; é um bug. Portanto, a maioria dos casos de uso deve chamar absolutamente em copy.deepcopy()vez de dict()ou dict.copy(). A resposta concisa de Imran está do lado certo da sanidade, ao contrário desta resposta.
Cecil Curry
647

Quando você atribui dict2 = dict1, não está fazendo uma cópia dict1, isso resulta em dict2apenas outro nome para dict1.

Para copiar os tipos mutáveis, como dicionários, use copy/ deepcopydo copymódulo.

import copy

dict2 = copy.deepcopy(dict1)
Imran
fonte
80
Para qualquer dicionário com o qual trabalho, é necessário o deepcopy ... Acabei de perder várias horas devido a um erro porque não estava recebendo uma cópia completa de um dicionário aninhado e minhas alterações nas entradas aninhadas estavam afetando o original .
precisa saber é o seguinte
7
O mesmo aqui. deepcopy () faz o truque. Estava bagunçando meus dados aninhados dentro de um cache rotativo adicionando um carimbo de data / hora a uma 'cópia' do evento original. Obrigado!
fxstein
8
Na verdade, isso deve ser marcado como a resposta correta; Esta resposta é geral e também funciona para um dicionário de dicionários.
precisa saber é
30
Essa deve ser a resposta aceita. A retórica infundada "cópia profunda é considerada prejudicial", incorporada na seção de comentários da resposta atual aceita, convida descaradamente problemas de sincronização ao copiar dicionários aninhados (como os documentados aqui) e deve ser contestada como tal.
Cecil Curry
deepcopy é o caminho a seguir no caso de uma estrutura de dicionário complexa. dict1.copy () simplesmente copia os valores das chaves como referências e não como objetos.
Rohith N
182

Enquanto dict.copy()e dict(dict1)gera uma cópia, elas são apenas cópias rasas . Se você deseja uma cópia profunda , copy.deepcopy(dict1)é necessária. Um exemplo:

>>> source = {'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> copy1 = x.copy()
>>> copy2 = dict(x)
>>> import copy
>>> copy3 = copy.deepcopy(x)
>>> source['a'] = 10  # a change to first-level properties won't affect copies
>>> source
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy3
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> source['b']['m'] = 40  # a change to deep properties WILL affect shallow copies 'b.m' property
>>> source
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy3  # Deep copy's 'b.m' property is unaffected
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

Em relação às cópias rasas e profundas, do módulo Python copydocs :

A diferença entre cópia superficial e profunda é relevante apenas para objetos compostos (objetos que contêm outros objetos, como listas ou instâncias de classe):

  • Uma cópia superficial constrói um novo objeto composto e, em seguida, (na medida do possível) insere referências aos objetos encontrados no original.
  • Uma cópia profunda constrói um novo objeto composto e, em seguida, recursivamente, insere cópias dos objetos encontrados no original.
gpanda
fonte
2
essa deve ser a resposta correta, pois não faz um loop explícito sobre o ditado e pode ser usada para outras estruturas primárias.
Nikkolasg
27
Apenas para esclarecer: w=copy.deepcopy(x)é a linha principal.
alcoholiday
Qual é a diferença entre dict2 = dict1e dict2 = copy.deepcopy(dict1)?
TheTank
1
@TheTank, y = x faz com que os dois nomes (referências) se refiram a um mesmo objeto, ou seja, "y é x" é True. Qualquer alteração feita no objeto através de x é equivalente a uma mesma alteração em y. No entanto, u, v, w são referências a novos objetos diferentes que possuem valores copiados de x durante a instanciação. Quanto às diferenças entre u, v (cópia superficial) e W (deepcopy), verifique docs.python.org/2/library/copy.html
gpanda
63

No python 3.5+, há uma maneira mais fácil de obter uma cópia superficial usando o ** operador de descompactação. Definido pelo Pep 448 .

>>>dict1 = {"key1": "value1", "key2": "value2"}
>>>dict2 = {**dict1}
>>>print(dict2)
{'key1': 'value1', 'key2': 'value2'}
>>>dict2["key2"] = "WHY?!"
>>>print(dict1)
{'key1': 'value1', 'key2': 'value2'}
>>>print(dict2)
{'key1': 'value1', 'key2': 'WHY?!'}

** descompacta o dicionário em um novo dicionário que é atribuído ao dict2.

Também podemos confirmar que cada dicionário tem um ID distinto.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Se uma cópia profunda for necessária, então copy.deepcopy () ainda é o caminho a percorrer.

PabTorre
fonte
3
Isso se parece muito com ponteiros em C ++. Agradável por realizar a tarefa, mas em termos de legibilidade, tenho tendência a não gostar deste tipo de operadores.
Ernesto
1
Ele tem um tipo de aparência c'ish ... mas ao mesclar vários dicionários, a sintaxe parece bastante suave.
PabTorre
2
Tenha cuidado com isso, ele executa apenas uma cópia superficial.
Sebastian Dressler
você está certo @SebastianDressler, vou fazer ajustes. thnx.
PabTorre
2
Útil se você deseja criar uma cópia com algumas especiarias:dict2 = {**dict1, 'key3':'value3'}
evg656e
48

As melhores e as maneiras mais fáceis de criar uma cópia de um dict no Python 2.7 e 3 são ...

Para criar uma cópia do dicionário simples (nível único):

1. Usando o método dict () , em vez de gerar uma referência que aponte para o dict existente.

my_dict1 = dict()
my_dict1["message"] = "Hello Python"
print(my_dict1)  # {'message':'Hello Python'}

my_dict2 = dict(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

2. Usando o método update () interno do dicionário python.

my_dict2 = dict()
my_dict2.update(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

Para criar uma cópia do dicionário aninhado ou complexo:

Use o módulo de cópia embutido , que fornece operações genéricas de cópia superficial e profunda. Este módulo está presente no Python 2.7 e 3. *

import copy

my_dict2 = copy.deepcopy(my_dict1)
AKay Nirala
fonte
6
Acredito que dict()cria uma cópia superficial e não profunda. Significando que se você tiver um aninhado dict, o exterior dictserá uma cópia, mas o ditado interno será uma referência ao ditado interno original.
shmuels
@shmuels sim, ambos os métodos criarão uma cópia superficial, não a profunda. Veja, a resposta atualizada.
Akay Nirala
37

Você também pode criar um novo dicionário com uma compreensão do dicionário. Isso evita a importação de cópia.

dout = dict((k,v) for k,v in mydict.items())

Obviamente, em python> = 2.7, você pode fazer:

dout = {k:v for k,v in mydict.items()}

Mas para compatibilidade com versões anteriores, o método top é melhor.

Dashing Adam Hughes
fonte
4
Isso é particularmente útil se você deseja ter mais controle sobre como e o que exatamente é copiado. 1
ApproachingDarknessFish
14
Observe que esse método não realiza uma cópia profunda e, se você deseja uma cópia superficial, sem a necessidade de controlar as chaves a serem copiadas, d2 = dict.copy(d1)também não exige nenhuma importação.
Jarek Piórkowski
1
@ JarekPiórkowski: ou você pode chamar um método como um método:d2 = d1.copy()
Azat Ibrakov
Observe que você não precisa da compreensão no primeiro exemplo. dict.itemsjá retorna um par de chave / valor iterável. Então você pode apenas usar dict(mydict.items())(você também pode apenas usar dict(mydict)). Pode ser útil ter compreensão se você deseja filtrar as entradas.
Paul Rooney
22

Além das outras soluções fornecidas, você pode usar **para integrar o dicionário em um dicionário vazio, por exemplo,

shallow_copy_of_other_dict = {**other_dict}.

Agora você terá uma cópia "rasa" de other_dict.

Aplicado ao seu exemplo:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = {**dict1}
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>>

Ponteiro: Diferença entre cópias rasas e profundas

d4rty
fonte
1
Isso resulta em uma cópia superficial, não em uma cópia profunda.
23418 sytech #
1
Eu estava tentando isso, mas estava tendo problemas. Isso funciona apenas para python 3.5 e superior. python.org/dev/peps/pep-0448
ThatGuyRob
19

As instruções de atribuição no Python não copiam objetos, elas criam ligações entre um destino e um objeto.

portanto, dict2 = dict1resulta em outra ligação entre dict2e o objeto quedict1 refere.

Se você deseja copiar um ditado, pode usar o copy module. O módulo de cópia possui duas interfaces:

copy.copy(x)
Return a shallow copy of x.

copy.deepcopy(x)
Return a deep copy of x.

A diferença entre cópia superficial e profunda é relevante apenas para objetos compostos (objetos que contêm outros objetos, como listas ou instâncias de classe):

Uma cópia superficial constrói um novo objeto composto e, em seguida, (na medida do possível) insere referências aos objetos encontrados no original.

Uma cópia profunda constrói um novo objeto composto e, em seguida, recursivamente, insere cópias dos objetos encontrados no original.

Por exemplo, no python 2.7.9:

>>> import copy
>>> a = [1,2,3,4,['a', 'b']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(5)
>>> a[4].append('c')

e o resultado é:

>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> d
[1, 2, 3, 4, ['a', 'b']]
afrouxar
fonte
10

Você pode copiar e editar a cópia recém-construída de uma só vez, chamando o dictconstrutor com argumentos adicionais de palavras-chave:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}
Frerich Raabe
fonte
9

Isso também me confundiu, inicialmente, porque eu era proveniente de um fundo C.

Em C, uma variável é um local na memória com um tipo definido. A atribuição a uma variável copia os dados no local de memória da variável.

Mas no Python, variáveis ​​agem mais como ponteiros para objetos. Portanto, atribuir uma variável a outra não faz uma cópia, apenas faz o nome da variável apontar para o mesmo objeto.

Craig McQueen
fonte
5
variáveis python agir mais como referências c ++
Ruggero Turra
7
Porque tudo em Python é um objeto! diveintopython.net/getting_to_know_python/... (sim, essa resposta é muitos anos de atraso, mas talvez seja de alguma utilidade para alguém!)
grimman
1
Eu acredito que a semântica da linguagem Python diz que não há "variáveis". Eles são chamados de "referências nomeadas"; significando que a referência a um objeto é uma sequência sintática no código. Um objeto pode ter muitas referências nomeadas a ele. Objetos imutáveis, como ints, floats e str, possuem apenas uma instância por processo. Um int de 1 em memória não mudar para um 2 ou algum outro valor no mesmo endereço de memória quando você faz isso myvalue = 1 myvalue = 2
DevPlayer
7

Toda variável em python (coisas como dict1ou strou __builtins__é um ponteiro para algum "objeto" platônico oculto dentro da máquina.

Se você definir dict1 = dict2, basta apontar dict1para o mesmo objeto (ou local da memória ou qualquer analogia que desejar) como dict2. Agora, o objeto referenciado por dict1é o mesmo objeto referenciado por dict2.

Você pode verificar: dict1 is dict2deve ser True. Além disso, id(dict1)deve ser o mesmo queid(dict2) .

Você quer dict1 = copy(dict2)oudict1 = deepcopy(dict2) .

A diferença entre copye deepcopy? deepcopygarantirá que os elementos dedict2 (você apontou para uma lista?) também sejam cópias.

Não uso deepcopymuito - geralmente é uma prática ruim escrever código que seja necessário (na minha opinião).

melancólico
fonte
Acabei de perceber que sempre preciso usar a cópia em profundidade para que, quando copio um dicionário aninhado e inicio a modificação de entradas aninhadas, os efeitos ocorram apenas na cópia e não no original.
precisa saber é o seguinte
6

dict1é um símbolo que faz referência a um objeto de dicionário subjacente. Atribuir dict1a dict2apenas atribui a mesma referência. Alterar o valor de uma chave por meio do dict2símbolo altera o objeto subjacente, o que também afetadict1 . Isso é confuso.

É muito mais fácil argumentar sobre valores imutáveis ​​do que referências, portanto, faça cópias sempre que possível:

person = {'name': 'Mary', 'age': 25}
one_year_later = {**person, 'age': 26}  # does not mutate person dict

É sintaticamente o mesmo que:

one_year_later = dict(person, age=26)
Petrus Theron
fonte
5

dict2 = dict1não copia o dicionário. Simplesmente fornece ao programador uma segunda maneira ( dict2) de se referir ao mesmo dicionário.


fonte
5
>>> dict2 = dict1
# dict2 is bind to the same Dict object which binds to dict1, so if you modify dict2, you will modify the dict1

Existem muitas maneiras de copiar o objeto Dict, eu simplesmente uso

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)
imcaozi
fonte
12
dict_2 = dict_1.copy()é muito mais eficiente e lógico.
Jean-François Fabre
2
Observe que, se você tiver um dict dentro de dict1, com dict_1.copy (), as alterações feitas no dict interno no dict_2 também serão aplicadas ao dict interno no dict_1. Nesse caso, você deve usar copy.deepcopy (dict_1).
queise
1

Como outros explicaram, o built-in dictnão faz o que você deseja. Porém, no Python2 (e provavelmente também no 3), você pode criar facilmente uma ValueDictclasse que copia =para ter certeza de que o original não será alterado.

class ValueDict(dict):

    def __ilshift__(self, args):
        result = ValueDict(self)
        if isinstance(args, dict):
            dict.update(result, args)
        else:
            dict.__setitem__(result, *args)
        return result # Pythonic LVALUE modification

    def __irshift__(self, args):
        result = ValueDict(self)
        dict.__delitem__(result, args)
        return result # Pythonic LVALUE modification

    def __setitem__(self, k, v):
        raise AttributeError, \
            "Use \"value_dict<<='%s', ...\" instead of \"d[%s] = ...\"" % (k,k)

    def __delitem__(self, k):
        raise AttributeError, \
            "Use \"value_dict>>='%s'\" instead of \"del d[%s]" % (k,k)

    def update(self, d2):
        raise AttributeError, \
            "Use \"value_dict<<=dict2\" instead of \"value_dict.update(dict2)\""


# test
d = ValueDict()

d <<='apples', 5
d <<='pears', 8
print "d =", d

e = d
e <<='bananas', 1
print "e =", e
print "d =", d

d >>='pears'
print "d =", d
d <<={'blueberries': 2, 'watermelons': 315}
print "d =", d
print "e =", e
print "e['bananas'] =", e['bananas']


# result
d = {'apples': 5, 'pears': 8}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
d = {'apples': 5, 'pears': 8}
d = {'apples': 5}
d = {'watermelons': 315, 'blueberries': 2, 'apples': 5}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
e['bananas'] = 1

# e[0]=3
# would give:
# AttributeError: Use "value_dict<<='0', ..." instead of "d[0] = ..."

Por favor, consulte o padrão de modificação lvalue discutido aqui: Python 2.7 - sintaxe limpa para modificação de lvalue . A principal observação é essa stre intse comporta como valores em Python (mesmo que sejam objetos imutáveis ​​sob o capô). Enquanto você observa isso, observe também que nada é magicamente especial sobre strou int. dictpode ser usado da mesma maneira, e posso pensar em muitos casos em que ValueDictfaz sentido.

personal_cloud
fonte
0

o código a seguir, que está no dict, que segue a sintaxe json mais de 3 vezes mais rápido que a cópia em profundidade

def CopyDict(dSrc):
    try:
        return json.loads(json.dumps(dSrc))
    except Exception as e:
        Logger.warning("Can't copy dict the preferred way:"+str(dSrc))
        return deepcopy(dSrc)
Carsten Thielepape
fonte
0

Corri para um comportamento peculiar ao tentar copiar profundamente a propriedade do dicionário da classe sem atribuí-la à variável

new = copy.deepcopy(my_class.a)não funciona, ou seja, modificar newmodificamy_class.a

mas se você faz old = my_class.ae então new = copy.deepcopy(old)ele funciona perfeitamente, ou seja, modificar newnão afetamy_class.a

Não sei por que isso acontece, mas espero que ajude a economizar algumas horas! :)

Anushk
fonte
Então, como você faz uma cópia em profundidade my_class.a?
Anthony
Não é o melhor caminho. Boa resposta é abaixo.
David Beauchemin
-1

porque, dict2 = dict1, dict2 mantém a referência a dict1. O dict1 e o dict2 apontam para o mesmo local na memória. Este é apenas um caso normal ao trabalhar com objetos mutáveis ​​em python. Ao trabalhar com objetos mutáveis ​​em python, você deve ter cuidado, pois é difícil depurar. Como o exemplo a seguir.

 my_users = {
        'ids':[1,2],
        'blocked_ids':[5,6,7]
 }
 ids = my_users.get('ids')
 ids.extend(my_users.get('blocked_ids')) #all_ids
 print ids#output:[1, 2, 5, 6, 7]
 print my_users #output:{'blocked_ids': [5, 6, 7], 'ids': [1, 2, 5, 6, 7]}

Este exemplo de intenção é obter todos os IDs de usuário, incluindo IDs bloqueados. Obtivemos da variável ids, mas também atualizamos o valor de my_users sem intenção. quando você estendeu os ids com blocked_ids my_users foi atualizado porque ids consulte my_users .

Vkreddy Komatireddy
fonte
-1

Copiando usando um loop for:

orig = {"X2": 674.5, "X3": 245.0}

copy = {}
for key in orig:
    copy[key] = orig[key]

print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 674.5, 'X3': 245.0}
copy["X2"] = 808
print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 808, 'X3': 245.0}
Jesper Joachim Sørensen
fonte
1
Isso funciona apenas para dicionários simples. Por que não usar deepcopy, que é construído expressamente para esse fim?
Anthony
Não é o melhor caminho. Boa resposta é abaixo.
David Beauchemin
-6

Você pode usar diretamente:

dict2 = eval(repr(dict1))

em que o objeto dict2 é uma cópia independente do dict1, para que você possa modificar o dict2 sem afetar o dict1.

Isso funciona para qualquer tipo de objeto.

Viiik
fonte
4
Esta resposta está incorreta e não deve ser usada. Uma classe definida pelo usuário, por exemplo, pode não ter um apropriado __repr__para ser reconstruído por eval, nem a classe do objeto pode estar no escopo atual a ser chamado. Mesmo aderindo a tipos internos, isso falhará se o mesmo objeto for armazenado em várias chaves, pois dict2haveria dois objetos separados. Um dicionário auto-referencial, onde se dict1contém, irá conter Ellipsis. Seria melhor usardict1.copy()
Eldritch Cheese
Não se espera que objetos (ou "valores") sempre tenham uma representação fiel por cadeias de caracteres, de maneira alguma habitualmente legível ao ser humano.
21418 Alexey