How to copy a Python class instance if deepcopy() does not work? How to copy a Python class instance if deepcopy() does not work? python python

How to copy a Python class instance if deepcopy() does not work?


Yes you can make a copy of class instance using deepcopy:

from copy import deepcopyc = C(4,5,'r'=2)d = deepcopy(c)

This creates the copy of class instance 'c' in 'd' .


One way to do that is by implementing __copy__ in the C Class like so:

class A:    def __init__(self):        self.var1 = 1        self.var2 = 2        self.var3 = 3class C(A):    def __init__(self, a=None, b=None, **kwargs):        super().__init__()        self.a = a        self.b = b        for x, v in kwargs.items():            setattr(self, x, v)    def __copy__(self):        self.normalizeArgs()        return C(self.a, self.b, kwargs=self.kwargs)    # THIS IS AN ADDITIONAL GATE-KEEPING METHOD TO ENSURE     # THAT EVEN WHEN PROPERTIES ARE DELETED, CLONED OBJECTS    # STILL GETS DEFAULT VALUES (NONE, IN THIS CASE)    def normalizeArgs(self):        if not hasattr(self, "a"):            self.a      = None        if not hasattr(self, "b"):            self.b      = None        if not hasattr(self, "kwargs"):            self.kwargs = {}cMain   = C(a=4, b=5, kwargs={'r':2})del cMain.bcClone  = cMain.__copy__()cMain.a = 11del  cClone.bcClone2 = cClone.__copy__()print(vars(cMain))print(vars(cClone))print(vars(cClone2))

enter image description here


I have mostly figured it out. The only problem which I cannot overcome is knowing an acceptable set of initialization arguments (arguments for __init__) for all classes. So I have to make the following two assumtions:

1) I have a set of default arguments for class C which I call argsC.2) All objects in C can be initialized with empty arguments.

In which case I canFirst:Initialize a new instance of the class C from it's instance which I want to copy c:

c_copy = c.__class__(**argsC)

Second:Go through all the attributes of c and set the attributes c_copy to be a copy of the attributes of c

for att in c.__dict__:    setattr(c_copy, att, object_copy(getattr(c,att)))

where object_copy is a recursive application of the function we are building.

Last:Delete all attributes in c_copy but not in c:

for att in c_copy.__dict__:    if not hasattr(c, att):        delattr(c_copy, att)

Putting this all together we have:

import copydef object_copy(instance, init_args=None):    if init_args:        new_obj = instance.__class__(**init_args)    else:        new_obj = instance.__class__()    if hasattr(instance, '__dict__'):        for k in instance.__dict__ :            try:                attr_copy = copy.deepcopy(getattr(instance, k))            except Exception as e:                attr_copy = object_copy(getattr(instance, k))            setattr(new_obj, k, attr_copy)        new_attrs = list(new_obj.__dict__.keys())        for k in new_attrs:            if not hasattr(instance, k):                delattr(new_obj, k)        return new_obj    else:        return instance

So putting it all together we have:

argsC = {'a':1, 'b':1}c = C(4,5,r=[[1],2,3])c.a = 11del c.bc_copy = object_copy(c, argsC)c.__dict__

{'a': 11, 'r': [[1], 2, 3]}

c_copy.__dict__

{'a': 11, 'r': [[1], 2, 3]}

c.__dict__

{'a': 11, 'r': [[1, 33], 2, 3]}

c_copy.__dict__

{'a': 11, 'r': [[1], 2, 3]}

Which is the desired outcome. It uses deepcopy if it can, but for the cases where it would raise an exception, it can do without.