0

I have a user defined dictionary (sub-classing python's built-in dict object), which does not allow modifying the dict directly:

class customDict(dict):
    """
    This dict does not allow the direct modification of
    its entries(e.g., d['a'] = 5 or del d['a'])
    """
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __setitem__(self,key,value):
        raise Exception('You cannot directly modify this dictionary. Use set_[property_name] method instead')

    def __delitem__(self,key):
        raise Exception('You cannot directly modify this dictionary. Use set_[property_name] method instead')

My problem is that I am not able to deep copy this dictionary using copy.deepcopy. Here's an example:

d1 = customDict({'a':1,'b':2,'c':3})
print d1
d2 = deepcopy(d1)
print d2

where it throws the exception I've defined myself for setitem:

Exception: You cannot directly modify this dictionary. Use set_[property_name] method instead

I tried overwriting deepcopy method as follows as suggested here:

def __deepcopy__(self, memo):
    cls = self.__class__
    result = cls.__new__(cls)
    memo[id(self)] = result
    for k, v in self.__dict__.items():
        setattr(result, k, deepcopy(v, memo))
    return result

This doesn't throw any errors but it returns an empty dictionary:

d1 = customDict({'a':1,'b':2,'c':3})
print d1
d2 = deepcopy(d1)
print d2

{'a': 1, 'c': 3, 'b': 2} 
{}

Any ideas how to fix this?

Community
  • 1
  • 1
user3076813
  • 499
  • 2
  • 6
  • 13
  • 4
    Inheritance is probably not a good solution for this. If your list doesn't do anything that a tuple doesn't already, just use a tuple. Otherwise use a proxy design pattern ("has a" not "is a") – wim May 03 '16 at 22:05
  • 2
    Why do you need to deep copy an immutable list? – Natecat May 03 '16 at 22:07
  • Also a good question! :) – wim May 03 '16 at 22:08
  • What you're asking is a legit question! Actually, I first created a user-defined dict that does not allow to directly modify its entries and then did the same for a list forgetting that I can use a tuple instead! I have edited the question and changed the user-defined list to the user-defined dict. – user3076813 May 04 '16 at 15:36
  • A related question is how to change the recursion limit for this user-defined deepcopy method when I am redefining deepcopy as "return customDict(deepcopy(dict(self)))". I am using sys.setrecursionlimit(10000) but still get this error: "RuntimeError: maximum recursion depth exceeded". I don't get this error though when using the other method suggested by malbarbo. – user3076813 May 04 '16 at 15:57

2 Answers2

2

Your deepcopy implementation does not work because the values of dict is not stored in __dict__. dict is a special class. You can make it work calling __init__ with a deepcopy of the dict.

def __deepcopy__(self, memo):
    def _deepcopy_dict(x, memo):
        y = {}
        memo[id(x)] = y
        for key, value in x.iteritems():
            y[deepcopy(key, memo)] = deepcopy(value, memo)
        return y
    cls = self.__class__
    result = cls.__new__(cls)
    result.__init__(_deepcopy_dict(self, memo))
    memo[id(self)] = result
    for k, v in self.__dict__.items():
        setattr(result, k, deepcopy(v, memo))
    return result

This program

d1 = customDict({'a': 2,'b': [3, 4]})
d2 = deepcopy(d1)
d2['b'].append(5)

print d1
print d2

Outputs

{'a': 2, 'b': [3, 4]}
{'a': 2, 'b': [3, 4, 5]}
malbarbo
  • 10,717
  • 1
  • 42
  • 57
  • It looks like `memo` stores the references of the originals, not the copies. Should it be `memo[id(self)] = self`, actually? The first answer to the link in the question also suggests it. – Mike Bessonov May 03 '16 at 22:40
  • No. See for example https://github.com/numpy/numpy/blob/090bbdd8f3debe0b6a6c4aa542937edf250f9443/numpy/ma/core.py#L5852 – malbarbo May 03 '16 at 22:55
  • This doesn't seem to work for a dictionary. Here's an example: d1 = customDict({'a':2,'b':[3,4]}) d2 = deepcopy(d1) d2['b'].append(5) print d1 print d2 {'a': 2, 'b': [3, 4, 5]} {'a': 2, 'b': [3, 4, 5]} print d2 – user3076813 May 06 '16 at 19:37
1

Something like this should work without having to change deepcopy.

x2 = customList(copy.deepcopy(list(x1)))

This will cast x1 to a list deepcopy it then make it a customList before assigning to x2.

Tom Myddeltyn
  • 1,307
  • 1
  • 13
  • 27
  • This is a great suggestion! The only thing is that what I am actually deepcopying is a class where this list serves as an instance variable. So, I still have to overwrite the deeopcoopy method. Nevertheless, I I just put: return customList(deepcopy(list(self))) in the definition of the deepcopy and it worked perfectly fine! – user3076813 May 04 '16 at 15:10
  • Instead of using customList(deepcopy(list(self))) I am using self.__init__(deepcopy(list(self))). It works fine but the problem that I have is that I get "RuntimeError: maximum recursion depth exceeded" even though I am using sys.setrecursionlimit(10000) – user3076813 May 06 '16 at 19:41
  • Why are you calling `__init__()` ? I think when you are calling `self.__init__(deepcopy(list(self)))` you are causing an infinite recursion. – Tom Myddeltyn May 06 '16 at 19:57
  • Sorry, I should have told you that I changed the question from customList to customDict. Like I mentioned in in my first comment, this customDict object that I have serves as an instance attribute of a class. Therefore, I would still need to overwrite the __deepcopy__ method in customDict to be able to deepcopy that class. deepcopy(dict(self)) converts the customDict object to a dictionary and then deepcopies it but then I have to convert it back to customDict and I thought I can do this by self.__init__(deepcopy(dict(self))) – user3076813 May 06 '16 at 20:06
  • You should be able to just call `customDict(deepcopy(dict(self)))` – Tom Myddeltyn May 06 '16 at 20:10
  • I get the same error with customDict(deepcopy(dict(self))). I need to emphasize that this does work for simple examples that I tested, but it doesn't work for the actual object that I have. where I always get "maximum recursion depth exceeded" even with customDict(deepcopy(dict(self))) and sys.setrecursionlimit(10000) – user3076813 May 06 '16 at 20:19