8

I have object which has it's own content (i.e. list of something) and a reference to another object, with which it is linked. How can I exclude the reference to the other object from being deep-copied?

from copy import deepcopy
class Foo:
    def __init__(self, content, linked_to):
        self.content = content
        self.linked_to = linked_to

a1 = Foo([[1,2],[3,4]], None)
a2 = Foo([[5,6],[7,8]], a1)

a3 = deepcopy(a2) # <- I don't want there, that a3.linked_to will be copied
# I want that a3.linked_to will still point to a1

a3.linked_to.content.append([9,10])
print a1.content # [[1,2],[3,4]], but I want [[1,2],[3,4], [9,10]] 
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Anton Ovsyannikov
  • 1,010
  • 1
  • 12
  • 30

1 Answers1

10

Your class can implement a __deepcopy__ method to control how it is copied. From the copy module documentation:

In order for a class to define its own copy implementation, it can define special methods __copy__() and __deepcopy__(). The former is called to implement the shallow copy operation; no additional arguments are passed. The latter is called to implement the deep copy operation; it is passed one argument, the memo dictionary. If the __deepcopy__() implementation needs to make a deep copy of a component, it should call the deepcopy() function with the component as first argument and the memo dictionary as second argument.

Simply return a new instance of your class, with the reference you don't want to be deep-copied just taken across as-is. Use the deepcopy() function to copy other objects:

from copy import deepcopy

class Foo:
    def __init__(self, content, linked_to):
        self.content = content
        self.linked_to = linked_to

    def __deepcopy__(self, memo):
        # create a copy with self.linked_to *not copied*, just referenced.
        return Foo(deepcopy(self.content, memo), self.linked_to)

Demo:

>>> a1 = Foo([[1, 2], [3, 4]], None)
>>> a2 = Foo([[5, 6], [7, 8]], a1)
>>> a3 = deepcopy(a2)
>>> a3.linked_to.content.append([9, 10])  # still linked to a1
>>> a1.content
[[1, 2], [3, 4], [9, 10]]
>>> a1 is a3.linked_to
True
>>> a2.content is a3.content  # content is no longer shared
False
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • how does the memo parameter fall into this? I'm struggling to get my head around how to do this for an arbritrary number of properties I want linked and not linked – Rugnir Nov 04 '21 at 00:05
  • @Rugnir you need to pass it along to other `deepcopy()` calls, nothing else. For every property you need to copy for the return value, call `deepcopy(value_to_copy, memo)`. – Martijn Pieters Jan 19 '22 at 00:39