Note that there are some explanatory texts on larger screens.

plurals
  1. POLimited deep copy of an instance with a container of containers as an attribute
    text
    copied!<p>I have a class</p> <ul> <li>whose instances have attributes that are containers <ul> <li>which themselves contain containers, each containing many items</li> </ul></li> <li>has an expensive initialization of these containers</li> </ul> <p>I want to create copies of instances such that</p> <ol> <li>the container attributes are copied, rather than shared as references, but</li> <li>the containers within each container are not deeply copied, but are shared references</li> <li>a call to the class's expensive <code>__init__()</code> method is avoided if possible</li> </ol> <p>For an example, let's use the class <code>SetDict</code>, below, which, when creating an instance, initializes a dictionary-like data structure as an attribute, <code>d</code>. <code>d</code> stores integers as keys and sets as values.</p> <pre><code>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) </code></pre> <p>I would like to copy instances of <code>SetDict</code>, such that <code>d</code> is itself copied, but the sets that are its values are <em>not</em> deep-copied, and are instead only references to the sets.</p> <p>For example, consider the following behavior currently for this class, where <code>copy.copy</code> doesn't copy the attribute <code>d</code> to the new copy, but <code>copy.deepcopy</code> creates completely new copies of the sets that are values of <code>d</code>.</p> <pre><code>&gt;&gt;&gt; import copy &gt;&gt;&gt; s = SetDict(3) &gt;&gt;&gt; s.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1])}) &gt;&gt;&gt; # Try a basic copy &gt;&gt;&gt; t = copy.copy(s) &gt;&gt;&gt; # Add a new key, value pair in t.d &gt;&gt;&gt; t.d[3] = set([2]) &gt;&gt;&gt; t.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])}) &gt;&gt;&gt; # But oh no! We unintentionally also added the new key to s.d! &gt;&gt;&gt; s.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])}) &gt;&gt;&gt; &gt;&gt;&gt; s = SetDict(3) &gt;&gt;&gt; # Try a deep copy &gt;&gt;&gt; u = copy.deepcopy(s) &gt;&gt;&gt; u.d[0].add(2) &gt;&gt;&gt; u.d[0] set([1, 2]) &gt;&gt;&gt; # But oh no! 2 didn't get added to s.d[0]'s set &gt;&gt;&gt; s.d[0] set([1]) </code></pre> <p>The behavior I'd like to see instead would be the following:</p> <pre><code>&gt;&gt;&gt; s = SetDict(3) &gt;&gt;&gt; s.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1])}) &gt;&gt;&gt; t = copy.copy(s) &gt;&gt;&gt; # Add a new key, value pair in t.d &gt;&gt;&gt; t.d[3] = set([2]) &gt;&gt;&gt; t.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1]), 3: set([2])}) &gt;&gt;&gt; # s.d retains the same key-value pairs &gt;&gt;&gt; s.d defaultdict(&lt;type 'set'&gt;, {0: set([1]), 1: set([1]), 2: set([1])}) &gt;&gt;&gt; t.d[0].add(2) &gt;&gt;&gt; t.d[0] set([1, 2]) &gt;&gt;&gt; # s.d[0] also had 2 added to its set &gt;&gt;&gt; s.d[0] set([1, 2]) </code></pre> <p>This was my first attempt to create a class that would do this, but it fails due to infinite recursion:</p> <pre><code>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 </code></pre> <p>I'm not sure how to properly override the <code>copy.copy</code> (or <code>copy.deepcopy</code>) behavior to achieve this. I'm also not entirely sure if I should be overriding <code>copy.copy</code> or <code>copy.deepcopy</code>. How can I go about getting the desired copy behavior?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload