How to copy a Python class instance if deepcopy() does not work?
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))
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.