37

I would like to make a copy of a class instance in python. I tried copy.deepcopy but I get the error message:

RuntimeError: Only Variables created explicitly by the user (graph leaves) support the deepcopy protocol at the moment

So suppose I have something like:

class C(object):
    def __init__(self,a,b, **kwargs):
        self.a=a
        self.b=b
        for x, v in kwargs.items():
            setattr(self, x, v)

c = C(4,5,'r'=2)
c.a = 11
del c.b

And now I want to make an identical deep copy of c, is there an easy way?

5
  • 1
    Yes. Definitely. Override the __copy__ dunder. Or the __deepcopy__ one, depending on what you need. Commented Jan 19, 2018 at 10:24
  • Yes, you can use copy.deepcopy. so just c2 = copy.deepcopy(c) then vars(c2) == {'a': 11, 'r': 2} and vars(c) == {'a': 11, 'r': 2} but the traceback your are reporting wouldn't be produced by the class definition you gave... Commented Jan 19, 2018 at 10:30
  • @cᴏʟᴅsᴘᴇᴇᴅ note, that isn't needed in this case. The copy module will handle types that don't define __copy__ or __deepcopy__ I don't think this has to do with that linked duplicate, the error message seems to suggest that deep-copy was purposefully overriden to throw the runtime error. Commented Jan 19, 2018 at 10:38
  • @juanpa.arrivillaga Huh... well... CV as "off-topic" then? I've already voted. Commented Jan 19, 2018 at 10:39
  • @juanpa.arrivillaga yes, in actual fact I am trying to make a copy of a meta-class of torch.nn.Module, but that is a bit complicated, so I am giving a simpler example here. Just stating that in my case deepcopy doesnt work, so i want another solution. Commented Jan 19, 2018 at 12:02

3 Answers 3

41

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

from copy import deepcopy

c = C(4,5,'r'=2)
d = deepcopy(c)

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

Sign up to request clarification or add additional context in comments.

4 Comments

I specifically mention that this doesn't work for me. Because in my case, deepcopy gives an error. I specifically don't want a solution using deepcopy.
I was directed here from google for searching "how to deepcopy an instance of a class" and this works for me. So, I have to upvote...
deepcopy uses pickle, which breaks on some objects: TypeError: can't pickle pygame.Surface objects -- which is specifically what I was searching for a workaround here. So this doesn't work for me.
@lode Only answers that answer the question are to be upvoted. This is a clear downvote since the answerer either did not read or did not understand the question. Upvoting this only because it was in the search results and helped you to deepcopy a class is no option, it should be downvoted instead.
15

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 = 3

class 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.b
cClone  = cMain.__copy__()

cMain.a = 11

del  cClone.b
cClone2 = cClone.__copy__()

print(vars(cMain))
print(vars(cClone))
print(vars(cClone2))

enter image description here

8 Comments

This doesn't work, I get the error ` 9 def __copy__(self): ---> 10 keywordArgs = vars(self)['kwargs'] 11 return C(self.a, self.b, kwargs=keywordArgs) 12 KeyError: 'kwargs'`
Not also that it should work even after I delete a variable. So after doing del c.a I should be able to do c_prime = c.__copy__().
And it should also deep copy the arguments. So if c.a == [1,2] I should not have c.a == c_prime.a.
it runs. But it doesn't solve my problem because, among other problems mentionned above. If I add the following line at the end of script: cClone = cMain.__copy__(), (i.e. after the del line), then I get the following error: AttributeError: 'C' object has no attribute 'b'
It also doesn't work if I exchange your line cMain = C(a=4, b=5, kwargs={'r':2}) for the line cMain = C(4,5,'r'=2) as I have in my question.
|
8

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 can First: 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 copy

def 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 = 11
del c.b
c_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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.