Deep Copy of a Dict in Python

Deep copy of a dict in python

How about:

import copy
d = { ... }
d2 = copy.deepcopy(d)

Python 2 or 3:

Python 3.2 (r32:88445, Feb 20 2011, 21:30:00) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import copy
>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = copy.deepcopy(my_dict)
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
3
>>>

Understanding dict.copy() - shallow or deep?

By "shallow copying" it means the content of the dictionary is not copied by value, but just creating a new reference.

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

In contrast, a deep copy will copy all contents by value.

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

So:

  1. b = a: Reference assignment, Make a and b points to the same object.

    Illustration of 'a = b': 'a' and 'b' both point to '{1: L}', 'L' points to '[1, 2, 3]'.

  2. b = a.copy(): Shallow copying, a and b will become two isolated objects, but their contents still share the same reference

    Illustration of 'b = a.copy()': 'a' points to '{1: L}', 'b' points to '{1: M}', 'L' and 'M' both point to '[1, 2, 3]'.

  3. b = copy.deepcopy(a): Deep copying, a and b's structure and content become completely isolated.

    Illustration of 'b = copy.deepcopy(a)': 'a' points to '{1: L}', 'L' points to '[1, 2, 3]'; 'b' points to '{1: M}', 'M' points to a different instance of '[1, 2, 3]'.

How to deep copy duplicate a list of dictionaries?

You could use the multiplication approach with dict.copy() assuming that your dicts are shallow like the ones in your example.

l = [{'k1': 'v1'}, {'k2': 'v2'}]

result = [d.copy() for d in l * 2]
result[0]['k1'] = 'changed'
print(result)
# [{'k1': 'changed'}, {'k2': 'v2'}, {'k1': 'v1'}, {'k2': 'v2'}]

Best way to partially deep copy a dictionary in Python?

Make a "1 level deep" copy of the dict by copying the keys and making copies of the sets. Replace this:

dict_class_copy = deepcopy(dict_class)

with this:

dict_class_copy = {k:set(v) for k,v in dict_class.items()}

and your tests pass.

Nested dictionaries copy() or deepcopy()?

When you're working with a mutable collection like a dictionary or a list, and you perform an assignment, you are not creating a copy of that object by default – i.e., the assignment of some dict b to another dict a creates a reference from b to the original object a, such that when you mutate b you indirectly also mutate a.

See this basic example:

>>> orig = {"a": 1, "b": 2}
>>> new = orig
>>> new["a"] = 9
>>> orig
{'a': 9, 'b': 2}
>>> new
{'a': 9, 'b': 2}
>>> new is orig
True

To fix this and keep the new and orig dictionaries separate objects that do not reference each other, make a deepcopy of orig when assigning it to new:

>>> import copy
>>> orig = {"a": 1, "b": 2}
>>> new = copy.deepcopy(orig)
>>> new["a"] = 9
>>> orig
{'a': 1, 'b': 2}
>>> new
{'a': 9, 'b': 2}
>>> new is orig
False

Also, here's a tl;dr for the Python documentation linked to above:

Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.

On creating new dictionaries in python: Using for_loops vs using deepcopy()

Deep copy is functionally different from using a for loop, which gives a shallow copy. If a dictionary contains any mutable objects, for example, lists, as values, then changing an element in a list changes the corresponding element in a shallow copy, but not in deep:

dic = {'1': [0, 1]}
shallow_copy = dic.copy()
from copy import deepcopy
deep_copy = deepcopy(dic)
dic['1'][0] = 1
shallow_copy
> {'1': [1, 1]}
deep_copy
> {'1': [0, 1]}

(Creating a copy dict via cycle gives essentially the same as dic.copy()) Of course, deep copy is generally slower, because it needs to copy all the elements at all levels of nesting, but in some cases you really want that change of a mutable element in the initial dict doesn't affect the copy - then use deepcopy. If all the values in the dict are immutable, there is no difference.

UPD: (Seeing your comments) Note that strings are immutable in Python.

How to clone / deepcopy Python 3.x dict with internal references to the objects

The correct way to clone custom data structures in Python is to implement the __deepcopy__ special method. This is what is called by the copy.deepcopy function.

As explained in the doc:

Two problems often exist with deep copy operations that don’t exist
with shallow copy operations:

  • Recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop.
  • Because deep copy copies everything it may copy too much, such as data which is intended to be shared between copies. [This is the problem you are facing]

The deepcopy() function avoids these problems by:

  • keeping a “memo” dictionary of objects already copied during the current copying pass; and
  • letting user-defined classes override the copying operation or the set of components copied.

Code

import copy

class A:
def __deepcopy__(self, memo):
return self.__class__()

class B:
def __init__(self, ref):
self.ref = ref

def __deepcopy__(self, memo):
return self.__class__(
ref=copy.deepcopy(self.ref, memo)
)

class Holder(dict):
def __deepcopy__(self, memo):
return self.__class__(
{k: copy.deepcopy(v, memo) for k, v in self.items()}
)

Test

import copy

original = Holder()
original['a'] = A()
original['b'] = B(original['a']) # here we create B object
# with reference to A object

assert original['a'] is original['b'].ref # reference is working

cp = copy.deepcopy(original) # we clone our dict

assert cp['a'] is cp['b'].ref # reference is still working

assert original['a'] is not cp['a']
assert original['b'] is not cp['b']
assert original['b'].ref is not cp['b'].ref


Related Topics



Leave a reply



Submit