497

I would like to make a deep copy of a dict in python. Unfortunately the .deepcopy() method doesn't exist for the dict. How do I do that?

>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = my_dict.deepcopy()
Traceback (most recent calll last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'deepcopy'
>>> my_copy = my_dict.copy()
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
7

The last line should be 3.

I would like that modifications in my_dict don't impact the snapshot my_copy.

How do I do that? The solution should be compatible with Python 3.x.

Olivier Grégoire
  • 31,286
  • 22
  • 93
  • 132
  • 3
    I don't know if it's a duplicate, but this: http://stackoverflow.com/questions/838642/python-dictionary-deepcopy is awfully close. – charleslparker Mar 14 '13 at 19:40

3 Answers3

704

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
>>>
James Bradbury
  • 1,590
  • 1
  • 18
  • 30
Lasse V. Karlsen
  • 366,661
  • 96
  • 610
  • 798
  • 18
    Indeed that works for the oversimplified example I gave. My keys are not numbers but objects. If I read the copy module documentation, I have to declare a __copy__()/__deepcopy__() method for the keys. Thank you very much for leading me there! – Olivier Grégoire Feb 24 '11 at 14:17
  • 4
    Is there any difference in Python 3.2 and 2.7 codes? They seem identical to me. If so, would be better a single block of code and a statement "Works for both Python 3 and 2" – MestreLion Jun 07 '14 at 03:59
  • 62
    It's also worth mentioning `copy.deepcopy` isn't thread safe. Learned this the hard way. On the other hand, depending on your use case, `json.loads(json.dumps(d))` _is_ thread safe, and works well. – rob Feb 07 '17 at 22:11
  • 8
    @rob you should post that comment as an answer. It is a viable alternative. The thread safety nuance is an important distinction. – BuvinJ Oct 23 '17 at 17:29
  • Of course, I guess one could also wrap `copy.deepcopy` in a homegrown thread safe function? – BuvinJ Oct 23 '17 at 17:30
  • 8
    @BuvinJ The issue is that `json.loads` doesn't solve the problem for all use cases where python `dict` attributes are not JSON serializable. It may help those who are only dealing with simple data structures, from an API for example, but I don't think it's enough of a solution to fully answer the OP's question. – rob Oct 24 '17 at 15:38
  • @rob, I understand the limitation. Thanks for posting your comment. You may have saved me some trouble. – BuvinJ Oct 24 '17 at 15:43
  • 2
    another fun fact: deepcopy of dict does not always preserve the order of elements: `d = dict() ; for n in range(8): d[(n,n+1)] = n ; assert d.keys() == copy.deepcopy(d).keys() ` – Francois Jan 03 '18 at 17:07
  • @rob Please can you convince me that JSON.dumps is threadsafe? This answer seems to contradict that hypothesis: https://stackoverflow.com/a/23781650 – Robino Jan 06 '18 at 15:07
  • What does the lack of thread safety mean here? i.e., would it be safe to copy `my_dict` from multiple threads, as long as `my_dict` isn't modified in the process? – kbolino Feb 05 '18 at 20:07
  • 1
    "thread safety" is a rather meaningless term, unless you use its opposite, "not thread safe". Thread safety implies expectations of behavior, but it doesn't detail *which* behavior. The thread safety a library author has in mind might not be the same a library user has in mind. For instance, is it enough that a collection doesn't corrupt itself if used on multiple threads? Do you need a check against `count > 0` followed by an implicit read of the first item to be consistent? In general, *reads* are thread safe as long as reading something doesn't have side-effects. – Lasse V. Karlsen Feb 06 '18 at 08:15
  • `json.loads(json.dumps(d))` makes integer key to str `{1: {2: 3}, 3: [1, 2, 3]}` -> `{'1': {'2': 3}, '3': [1, 2, 3]}` – 구마왕 Feb 14 '22 at 06:15
57

dict.copy() is a shallow copy function for dictionary
id is built-in function that gives you the address of variable

First you need to understand "why is this particular problem is happening?"

In [1]: my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}

In [2]: my_copy = my_dict.copy()

In [3]: id(my_dict)
Out[3]: 140190444167808

In [4]: id(my_copy)
Out[4]: 140190444170328

In [5]: id(my_copy['a'])
Out[5]: 140190444024104

In [6]: id(my_dict['a'])
Out[6]: 140190444024104

The address of the list present in both the dicts for key 'a' is pointing to same location.
Therefore when you change value of the list in my_dict, the list in my_copy changes as well.


Solution for data structure mentioned in the question:

In [7]: my_copy = {key: value[:] for key, value in my_dict.items()}

In [8]: id(my_copy['a'])
Out[8]: 140190444024176

Or you can use deepcopy as mentioned above.

theBuzzyCoder
  • 2,374
  • 2
  • 26
  • 26
  • 7
    Your solution doesn't work for nested dictionaries. deepcopy is preferable for that reason. – Charles Plager Apr 18 '18 at 18:03
  • 2
    @CharlesPlager Agreed! But you should also notice that list slicing doesn't work on dict `value[:]`. The solution was for the particular data structure mentioned in the question rather than a universal solution. – theBuzzyCoder Apr 23 '18 at 10:06
  • 2nd solution for dict comprehension would not work with nested lists as values ``` >>> my_copy = {k:v.copy() for k,v in my_dict.items()} >>> id(my_copy["a"]) 4427452032 >>> id(my_dict["a"]) 4427460288 >>> id(my_copy["a"][0]) 4427469056 >>> id(my_dict["a"][0]) 4427469056 ``` – Aseem Jain Mar 04 '21 at 03:27
36

Python 3.x

from copy import deepcopy

my_dict = {'one': 1, 'two': 2}
new_dict_deepcopy = deepcopy(my_dict)

Without deepcopy, I am unable to remove the hostname dictionary from within my domain dictionary.

Without deepcopy I get the following error:

"RuntimeError: dictionary changed size during iteration"

...when I try to remove the desired element from my dictionary inside of another dictionary.

import socket
import xml.etree.ElementTree as ET
from copy import deepcopy

domain is a dictionary object

def remove_hostname(domain, hostname):
    domain_copy = deepcopy(domain)
    for domains, hosts in domain_copy.items():
        for host, port in hosts.items():
           if host == hostname:
                del domain[domains][host]
    return domain

Example output: [orginal]domains = {'localdomain': {'localhost': {'all': '4000'}}}

[new]domains = {'localdomain': {} }}

So what's going on here is I am iterating over a copy of a dictionary rather than iterating over the dictionary itself. With this method, you are able to remove elements as needed.

Adrian W
  • 3,993
  • 11
  • 35
  • 44
xpros
  • 1,826
  • 17
  • 15