Limited deep copy instance with container container as attribute

I have a class

  • whose instances have attributes that are containers
    • which themselves contain containers, each of which contains many elements
  • has expensive initialization of these containers.

I want to create copies of instances so

  • container attributes are copied, not separated as links, but
  • the containers inside each container are not heavily copied, but are common links
  • avoids calling an expensive __init__()class method

As an example, you can use the class below SetDict, which, when creating an instance, initializes a data structure, similar to a dictionary, as an attribute d. dstores integers as keys and sets them as values.

import collections

class SetDict(object):
    def __init__(self, size):
        self.d = collections.defaultdict(set)
        # Do some initialization; if size is large, this is expensive
        for i in range(size):
            self.d[i].add(1)

I would like to copy instances SetDict, so I dcopy myself, but the sets that are its values โ€‹โ€‹are not deeply copied and instead are just references to sets.

For example, consider the following behavior for this class, where it copy.copydoes not copy the attribute dto a new copy, but copy.deepcopycreates completely new copies of the sets, which are values d.

>>> import copy
>>> s = SetDict(3)
>>> s.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})
>>> # Try a basic copy
>>> t = copy.copy(s)
>>> # Add a new key, value pair in t.d
>>> t.d[3] = set([2])
>>> t.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])})
>>> # But oh no! We unintentionally also added the new key to s.d!
>>> s.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])})
>>> 
>>> s = SetDict(3)
>>> # Try a deep copy
>>> u = copy.deepcopy(s)
>>> u.d[0].add(2)
>>> u.d[0]
set([1, 2])
>>> # But oh no! 2 didn't get added to s.d[0] set
>>> s.d[0]
set([1])

The behavior I would like to see would be as follows:

>>> s = SetDict(3)
>>> s.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})
>>> t = copy.copy(s)
>>> # Add a new key, value pair in t.d
>>> t.d[3] = set([2])
>>> t.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])})
>>> # s.d retains the same key-value pairs
>>> s.d
defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})
>>> t.d[0].add(2)
>>> t.d[0]
set([1, 2])
>>> # s.d[0] also had 2 added to its set
>>> s.d[0]
set([1, 2])

, , - :

class CopiableSetDict(SetDict):
    def __copy__(self):
        import copy
        # This version gives infinite recursion, but conveys what we
        # intend to do.
        #
        # First, create a shallow copy of this instance
        other = copy.copy(self)
        # Then create a separate shallow copy of the d
        # attribute
        other.d = copy.copy(self.d)
        return other

, copy.copy ( copy.deepcopy) . , copy.copy copy.deepcopy. ?

+3
3

. SetDict(3), SetDict.__call__ SetDict.__new__(SetDict), __init__(3) __new__, SetDict. , SetDict ( ) , .

, . - .

import collections
import copy

class SetDict(object):
    def __init__(self, size):
        self.d = collections.defaultdict(set)
        # Do some initialization; if size is large, this is expensive
        for i in range(size):
            self.d[i].add(1)

    def __copy__(self):
        other = SetDict.__new__(SetDict) 
        other.d = self.d.copy()
        return other

__new__ , . , , __new__, - , , , . , .

t = SetDict(3)
print t.d  # defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})

s = copy.copy(t)
print s.d # defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})

t.d[3].add(1)
print t.d # defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([1])})
print s.d # defaultdict(<type 'set'>, {0: set([1]), 1: set([1]), 2: set([1])})

s.d[0].add(2)
print t.d[0] # set([1, 2])
print s.d[0] # set([1, 2])
+3

, __init__ copying=False. True, . -

class Foo(object):
    def __init__(self, value, copying=False):
        if copying:
            return
        self.value = value

    def __copy__(self):
       other = Foo(0, copying=True)
       other.value = self.value
       return other

, __init__ , __init__, , , .

+1

Based on the aaronsterling solution, I prepared the following, which, I think, is more flexible if there are other attributes associated with the instance:

class CopiableSetDict(SetDict):
    def __copy__(self):
        # Create an uninitialized instance
        other = self.__class__.__new__(self.__class__)
        # Give it the same attributes (references)
        other.__dict__ = self.__dict__.copy()
        # Create a copy of d dict so other can have its own
        other.d = self.d.copy()
        return other
+1
source

Source: https://habr.com/ru/post/1777239/


All Articles