Reputation: 113
I am trying to implement something analogous to how if x
is a list then y = list(x)
should be a deepcopy of x
.
So far I have the following class code:
from copy import deepcopy
class CustomClass:
def __init__(self, foo):
if isinstance(foo, CustomClass):
self = deepcopy(foo)
else:
self._foo = foo
Now if I run
A = CustomClass(bar)
B = CustomClass(A)
print(B._foo)
I receive an error AttributeError: 'CustomClass' object has no attribute '_foo'
. I'm not exactly sure what is going wrong here. If for example I were to add the line print(self._foo)
inside the if statement I would see that the deepcopy was successfully assigned to self. Is there a way to implement this deepcopy functionality?
Upvotes: 1
Views: 816
Reputation: 6891
As have been said in the comments, assigning to self
does not help. The issue is that when __init__(...)
is called, the class is already created and a reference is kept outside the class (i.e. by the Python interpreter/runtime). And, since __init__(...)
does not return anything (and specifically not its own reference) there is no way to change the external reference from there, which your attempt to deepcopy
does.
The object creation in Python, when calling e.g. CustomClass(foo)
, is actually a two step process. First, before the call to __init__(...)
the runtime calls another, statical method on the class, namely __new__(...)
. This is where the actual object is constructed and this class method actually do return a reference to the object. Thus, in theory it should be possible to do a deepcopy
in the __new__(...)
method (if you know how to do it). However, after the call to __new__(...)
returns, the runtime will continue the creation process and call the __init__(...)
method. Thus, in this case you would also need to ensure that this call does not change anything in the newly copied object.
A simpler way, though, is to allow the system to create a new object for you (by its automatic call to __new__(...)
) and then "form" the new image into a (deep copied) image of the orignal object. This can be done by deep copying all the attributes of the original object and put them in the new one. If all attributes are known, this can be done by explicitly listing them and assign the (deepcopied) calues to them:
class CustomClass:
def __init__(self, foo):
if isinstance(foo, CustomClass):
# if foo is CustomClass, deepcopy all its attributes
self._foo = deepcopy(foo._foo)
# return to not perform "normal" initialization
return
# This will only execute if foo is _not_ a CustomClass object
self._foo = foo
However, this approach is a bit fragile, as it requires that no attribute is forgotten in the copying process, and that no new attributes are added dynamically (or they will not be copied) if e.g. someone is doing somethin like
cc = CustomClass(4)
cc.dynamic = 5
cc.dynmaic
will not be copied (whereas deepcopy
will). A better approach is therefore to use the internal dictionary that keep a reference to all attributes, whenever they are created:
class CustomClass:
def __init__(self, foo):
if isinstance(foo, CustomClass):
# if foo is CustomClass, deepcopy all its attributes
self.__dict__ = {key: deepcopy(value) for key, value in foo.__dict__.items()}
# return to not perform "normal" initialization
return
# This will only execute if foo is _not_ a CustomClass object
self._foo = foo
Here the internal __dict__
of the object is used. It references all attributes, including any that has been dynamically added, and then I deepcopy
each of them and assign them to our newly created object, mimicing the behavior of a "normal" deepcopy
call.
Upvotes: 1