Cómo copiar un diccionario y solo editar la copia

856

¿Alguien puede explicarme esto? Esto no tiene ningún sentido para mí.

Copio un diccionario en otro y edito el segundo y ambos cambian. ¿Por qué está pasando esto?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}
MadSc13ntist
fuente
44
PythonTutor es ideal para visualizar referencias de Python. Aquí está este código en el último paso . Puede ver dict1y dict2señalar el mismo dict.
wjandrea

Respuestas:

883

Python nunca copia implícitamente objetos. Cuando establece dict2 = dict1, hace que se refieran al mismo objeto dict exacto, por lo que cuando lo muta, todas las referencias se refieren al objeto en su estado actual.

Si desea copiar el dict (lo cual es raro), debe hacerlo explícitamente con

dict2 = dict(dict1)

o

dict2 = dict1.copy()
Mike Graham
fuente
26
Puede ser mejor decir "dict2 y dict1 apuntan al mismo diccionario", no está cambiando dict1 o dict2 sino a qué apuntan.
GrayWizardx
276
También tenga en cuenta que dict.copy () es poco profundo, si hay una lista anidada / etc allí, los cambios se aplicarán a ambos. IIRC. Deepcopy lo evitará.
Se
16
No es del todo correcto que Python nunca copie implícitamente objetos. Los tipos de datos primitivos, como int, float y bool, también se tratan como objetos (solo haga una dir(1)para ver eso), pero se copian implícitamente.
daniel kullmann
17
@danielkullmann, creo que puede haber malentendidos sobre Python en función de cómo funcionan otros idiomas con los que ha trabajado. En Python, a) No existe el concepto de "tipos de datos primitivos". int, floaty las boolinstancias son objetos reales de Python, y b) los objetos de estos tipos no se copian implícitamente cuando los pasa, ni a un nivel semántico de Python con seguridad, ni siquiera como un detalle de implementación en CPython.
Mike Graham
39
La retórica sin fundamento como "La copia profunda se considera dañina" no es útil. En igualdad de condiciones, la copia superficial de una estructura de datos compleja es mucho más probable que produzca problemas inesperados de caso límite que la copia profunda de la misma estructura. Una copia en la que las modificaciones modifican el objeto original no es una copia; Es un error. Ergo, la mayoría de los casos de uso deberían llamar en copy.deepcopy()lugar de dict()o dict.copy(). La respuesta concisa de Imran está en el lado correcto de la cordura, a diferencia de esta respuesta.
Cecil Curry
647

Cuando asigna dict2 = dict1, no está haciendo una copia de dict1, resulta en dict2ser solo otro nombre para dict1.

Para copiar los tipos mutables como diccionarios, use copy/ deepcopydel copymódulo.

import copy

dict2 = copy.deepcopy(dict1)
Imran
fuente
80
Para cualquier diccionario con el que trabajo, lo que necesito es hacer una copia profunda ... Perdí varias horas debido a un error que se debía a que no estaba obteniendo una copia completa de un diccionario anidado y mis cambios en las entradas anidadas afectaban al original. .
flutefreak7
77
Igual que aquí. deepcopy () hace el truco. Estaba arruinando mis dictados anidados dentro de un caché giratorio al agregar una marca de tiempo a una 'copia' del evento original. ¡Gracias!
fxstein
8
Esto en realidad debería marcarse como la respuesta correcta; Esta respuesta es general y también funciona para un diccionario de diccionarios.
orezvani
30
Esta debería ser la respuesta aceptada. La retórica sin fundamento "La copia profunda se considera dañina" incrustada en la sección de comentarios de la respuesta actual aceptada invita descaradamente a problemas de sincronización al copiar diccionarios anidados (como los documentados aquí) y debe ser cuestionada como tal.
Cecil Curry
Deepcopy es el camino a seguir en caso de una compleja estructura de diccionario. dict1.copy () simplemente copia los valores de las claves como referencias y no como objetos.
Rohith N
182

Mientras dict.copy(), y dict(dict1)genera una copia, sólo son poco profundas copias. Si desea una copia profunda , copy.deepcopy(dict1)se requiere. Un ejemplo:

>>> 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}}

Con respecto a las copias superficiales y profundas, de los documentos del módulo Pythoncopy :

La diferencia entre copia superficial y profunda solo es relevante para objetos compuestos (objetos que contienen otros objetos, como listas o instancias de clase):

  • Una copia superficial construye un nuevo objeto compuesto y luego (en la medida de lo posible) inserta referencias en él a los objetos encontrados en el original.
  • Una copia profunda construye un nuevo objeto compuesto y luego, recursivamente, inserta copias en él de los objetos encontrados en el original.
gpanda
fuente
2
esta debería ser la respuesta correcta, ya que no se repite explícitamente sobre el dict y se puede usar para otras estructuras primarias.
Nikkolasg
27
Solo para aclarar: w=copy.deepcopy(x)es la línea clave.
alcoholiday
¿Cuál es la diferencia entre dict2 = dict1y dict2 = copy.deepcopy(dict1)?
TheTank
1
@TheTank, y = x hace que los dos nombres (referencias) se refieran a un mismo objeto, es decir, "y es x" es verdadero. Cualquier cambio realizado en el objeto a través de x es equivalente a un mismo cambio a través de y. Sin embargo, u, v, w son referencias a nuevos objetos diferentes que tienen valores copiados de x durante la creación de instancias. En cuanto a las diferencias entre u, v (copia superficial) yw (copia profunda), consulte docs.python.org/2/library/copy.html
gpanda
63

En python 3.5+ hay una manera más fácil de lograr una copia superficial utilizando el operador de desempaquetado **. Definido por 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?!'}

** desempaqueta el diccionario en un nuevo diccionario que luego se asigna a dict2.

También podemos confirmar que cada diccionario tiene una identificación distinta.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Si se necesita una copia profunda, copy.deepcopy () sigue siendo el camino a seguir.

PabTorre
fuente
3
Esto se ve terriblemente como punteros en C ++. Agradable para realizar la tarea, pero en términos de legibilidad, tiendo a que no me gusten este tipo de operadores.
Ernesto
1
Tiene una especie de aspecto c'ish ... pero al combinar varios diccionarios, la sintaxis se ve bastante suave.
PabTorre
2
Tenga cuidado con eso, solo realiza una copia superficial.
Sebastian Dressler
tienes razón @SebastianDressler, haré ajustes. gracias.
PabTorre
2
Útil si desea crear una copia con algunas especias:dict2 = {**dict1, 'key3':'value3'}
evg656e
48

Las mejores y más fáciles formas de crear una copia de un dict en Python 2.7 y 3 son ...

Para crear una copia del diccionario simple (de un solo nivel):

1. Usando el método dict () , en lugar de generar una referencia que apunte al 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 el método incorporado update () del diccionario 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 crear una copia de un diccionario anidado o complejo:

Utilice el módulo de copia incorporado , que proporciona operaciones genéricas de copia superficial y profunda. Este módulo está presente en Python 2.7 y 3. *

import copy

my_dict2 = copy.deepcopy(my_dict1)
AKay Nirala
fuente
66
Creo que dict()crea una copia superficial, no una copia profunda. Lo que significa que si tiene un anidado dict, el exterior dictserá una copia, pero el dict interno será una referencia al dict interno original.
shmuels
@shmuels sí, ambos métodos crearán una copia superficial, no la profunda. Ver, la respuesta actualizada.
AKay Nirala
37

También puede crear un nuevo diccionario con una comprensión del diccionario. Esto evita importar la copia.

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

Por supuesto, en python> = 2.7 puedes hacer:

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

Pero para la compatibilidad con versiones anteriores, el método superior es mejor.

Dashing Adam Hughes
fuente
44
Esto es particularmente útil si desea tener más control sobre cómo y qué se copia exactamente. +1
ApproachingDarknessFish
14
Tenga en cuenta que este método no realiza una copia profunda, y si desea una copia superficial sin necesidad de controlar las claves para copiar, d2 = dict.copy(d1)tampoco requiere ninguna importación.
Jarek Piórkowski
1
@ JarekPiórkowski: o puede llamar a un método como un método:d2 = d1.copy()
Azat Ibrakov
Tenga en cuenta que no necesita la comprensión en el primer ejemplo. dict.itemsya devuelve un par clave / valor iterable. Así que solo puedes usar dict(mydict.items())(también puedes usar dict(mydict)). Puede ser útil tener la comprensión si desea filtrar las entradas.
Paul Rooney
22

Además de las otras soluciones proporcionadas, puede utilizar **para integrar el diccionario en un diccionario vacío, por ejemplo,

shallow_copy_of_other_dict = {**other_dict}.

Ahora tendrá una copia "superficial" de other_dict.

Aplicado a tu ejemplo:

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

Puntero: diferencia entre copias superficiales y profundas

d4rty
fuente
1
Esto da como resultado una copia superficial, no una copia profunda.
sytech
1
Estaba intentando esto pero tenía problemas. Esto solo funciona para python 3.5 y versiones posteriores. python.org/dev/peps/pep-0448
ThatGuyRob
19

Las declaraciones de asignación en Python no copian objetos, crean enlaces entre un objetivo y un objeto.

entonces, dict2 = dict1resulta otro enlace entre dict2y el objeto al que se dict1refieren.

si quieres copiar un dict, puedes usar el copy module. El módulo de copia tiene dos interfaces:

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

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

La diferencia entre copia superficial y profunda solo es relevante para objetos compuestos (objetos que contienen otros objetos, como listas o instancias de clase):

Una copia superficial construye un nuevo objeto compuesto y luego (en la medida de lo posible) inserta referencias en él a los objetos encontrados en el original.

Una copia profunda construye un nuevo objeto compuesto y luego, recursivamente, inserta copias de los objetos encontrados en el original.

Por ejemplo, en 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')

y el resultado es:

>>> 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']]
aflojar
fuente
10

Puede copiar y editar la copia recién construida de una vez llamando al dictconstructor con argumentos de palabras clave adicionales:

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

Esto también me confundió, inicialmente, porque venía de un fondo C.

En C, una variable es una ubicación en la memoria con un tipo definido. La asignación a una variable copia los datos en la ubicación de memoria de la variable.

Pero en Python, las variables actúan más como punteros a los objetos. Entonces, asignar una variable a otra no hace una copia, solo hace que el nombre de la variable apunte al mismo objeto.

Craig McQueen
fuente
55
las variables de Python actúan más como referencias de c ++
Ruggero Turra
77
¡Porque todo en Python es un objeto! diveintopython.net/getting_to_know_python/… (sí, esta respuesta lleva muchos años de retraso, ¡pero tal vez sea de alguna utilidad para alguien!)
grimman
1
Creo que la semántica del lenguaje Python dice que no hay "variables". Se llaman "referencias nombradas"; lo que significa que la referencia a un objeto es una cadena sintáctica en el código. Un objeto puede tener muchas referencias con nombre. Los objetos inmutables como ints y floats y las instancias str solo tienen una instancia por proceso. Un int de 1 en la memoria no cambia a 2 o algún otro valor en la misma dirección de memoria cuando hace esto myvalue = 1 myvalue = 2
DevPlayer
7

Cada variable en Python (cosas como dict1o stro __builtins__es un puntero a algún "objeto" platónico oculto dentro de la máquina.

Si configura dict1 = dict2, solo apunta dict1al mismo objeto (o ubicación de memoria, o cualquier analogía que desee) como dict2. Ahora, el objeto al que hace referencia dict1es el mismo objeto al que hace referencia dict2.

Puede verificar: dict1 is dict2debería ser True. Además, id(dict1)debe ser lo mismo que id(dict2).

Tu quieres dict1 = copy(dict2), o dict1 = deepcopy(dict2).

La diferencia entre copyy deepcopy? deepcopyse asegurará de que los elementos de dict2(¿lo señaló en una lista?) también sean copias.

No uso deepcopymucho; por lo general, es una mala práctica escribir código que lo necesita (en mi opinión).

melancólico
fuente
Me acabo de dar cuenta de que siempre necesito usar una copia profunda para que cuando copie un diccionario anidado y empiece a modificar las entradas anidadas, los efectos solo se producen en la copia y no en el original.
flutefreak7
6

dict1es un símbolo que hace referencia a un objeto de diccionario subyacente. Asignar dict1a dict2simplemente asigna la misma referencia. Cambiar el valor de una clave a través del dict2símbolo cambia el objeto subyacente, lo que también afecta dict1. Esto es confuso.

Es mucho más fácil razonar sobre valores inmutables que referencias, así que haga copias siempre que sea posible:

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

Esto es sintácticamente lo mismo que:

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

dict2 = dict1No copia el diccionario. Simplemente le da al programador una segunda forma ( dict2) para referirse al mismo diccionario.


fuente
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

Hay muchas formas de copiar objetos Dict, simplemente uso

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)
imcaozi
fuente
12
dict_2 = dict_1.copy()Es mucho más eficiente y lógico.
Jean-François Fabre
2
Tenga en cuenta que si tiene un dict dentro de dict1, con dict_1.copy () los cambios que realiza en el dict interno en dict_2 también se aplican al dict interno en dict_1. En este caso, debe usar copy.deepcopy (dict_1) en su lugar.
Queise
1

Como otros han explicado, el incorporado dictno hace lo que quieres. Pero en Python2 (y probablemente también en el 3) puedes crear fácilmente una ValueDictclase con la que copiar =para que puedas estar seguro de que el original no cambiará.

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] = ..."

Consulte el patrón de modificación de lvalue discutido aquí: Python 2.7 - sintaxis limpia para la modificación de lvalue . La observación clave es eso stry intcomportarse como valores en Python (a pesar de que en realidad son objetos inmutables bajo el capó). Mientras observa eso, tenga en cuenta también que nada es mágicamente especial sobre stro int. dictpuede usarse de la misma manera, y puedo pensar en muchos casos en los que ValueDicttiene sentido.

nube_personal
fuente
0

el siguiente código, que se encuentra en los dictados que sigue la sintaxis json más de 3 veces más rápido que la copia profunda

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
fuente
0

Me encontré con un comportamiento peculiar al intentar copiar en profundidad la propiedad del diccionario de la clase sin asignarla a la variable

new = copy.deepcopy(my_class.a)no funciona, es decir, modificar newmodificamy_class.a

pero si lo hace old = my_class.ay new = copy.deepcopy(old)funciona perfectamente, es decir, modificar newno afectamy_class.a

No estoy seguro de por qué sucede esto, ¡pero espero que ayude a ahorrar algunas horas! :)

Anushk
fuente
Entonces, ¿cómo se hace una copia profunda de my_class.a?
Anthony
No es la mejor manera. Buena respuesta es abajo.
David Beauchemin
-1

porque, dict2 = dict1, dict2 contiene la referencia a dict1. Tanto dict1 como dict2 apuntan a la misma ubicación en la memoria. Este es solo un caso normal mientras se trabaja con objetos mutables en python. Cuando trabaje con objetos mutables en python, debe tener cuidado ya que es difícil de depurar. Tal como el siguiente ejemplo.

 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]}

La intención de este ejemplo es obtener todos los ID de usuario, incluidos los ID bloqueados. Eso lo obtuvimos de la variable ids pero también actualizamos el valor de my_users sin querer. cuando extendió los identificadores con blocks_ids, my_users se actualizaron porque los identificadores se refieren a my_users .

Vkreddy Komatireddy
fuente
-1

Copiar usando un bucle 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
fuente
1
Esto solo funciona para diccionarios simples. ¿Por qué no usar deepcopy, que está construido expresamente para este propósito?
Anthony
No es la mejor manera. Buena respuesta es abajo.
David Beauchemin
-6

Puedes usar directamente:

dict2 = eval(repr(dict1))

donde objeto dict2 es una copia independiente de dict1, por lo que puede modificar dict2 sin afectar a dict1.

Esto funciona para cualquier tipo de objeto.

Viiik
fuente
44
Esta respuesta es incorrecta y no debe usarse. Una clase definida por el usuario, por ejemplo, puede no tener un apropiado __repr__para ser reconstruido por eval, ni la clase del objeto puede estar en el alcance actual para ser llamado. Incluso si se apega a los tipos incorporados, esto fallará si el mismo objeto se almacena en varias claves, ya dict2que entonces tendría dos objetos separados. Un diccionario autorreferencial, donde se dict1contiene, contendrá en su lugar Ellipsis. Sería mejor usarlodict1.copy()
Eldritch Cheese
No se espera que los objetos (o "valores") siempre tengan una representación fiel por cadenas de caracteres, no de una manera legible por los humanos en ningún caso.
Alexey