Search code examples
pythonclassdeep-copy

How to deep-copy a class object in Python3


GIVEN:

class A:
   x = 4711
B = COPY(A)
setattr(B, "x", "0815")
print("A: %s; B: %s;" % (A.x, B.x))

GOAL:

An operation COPY such that the code fragment above results in

A: 4711; B: 0815;

IN PLAIN WORDS:

By what means can a class object be deep-copied, so that it is totally isolated from its original. Using copy.deepcopy() delivers

A: 0185; B: 0185;

so that is not the solution.


Solution

  • from copy import deepcopy
    
    class A:
        x = 123
        def __init__(self):
            self.f()
        def f(self):
            print("original function", self.x)
    
    def g(self):
        print("replacement function", self.x)
    
    B = deepcopy(A)
    B.x = 456
    B.f = g
    
    a = A()
    b = B()
    

    This example prints:

    replacement function 456
    replacement function 456
    

    Apparently, both A as well as B share the same values for their attributes x and f. Therefore, as you have already noted, copy.deepcopy doesn't work to "copy" a class object. It seems that Python class objects are singletons, because A is deepcopy(A) is True.

    So, alternatively, you could just use inheritance instead of copying:

    class A:
        x = 123
        def __init__(self):
            self.f()
        def f(self):
            print("original function", self.x)
    
    def g(self):
        print("replacement function", self.x)
    
    class B(A):
        pass
    B.x = 456
    B.f = g
    
    a = A()
    b = B()
    

    Which prints:

    original function 123
    replacement function 456
    

    Like this, we are able to change B.x and B.f without affecting A.x and A.f.

    However, isinstance(b, A) will be True, which might be undesired. Also, changes to class attributes of A will propagate to its child B. Therefore, you just change your original A into a dummy A_, first, and then derive both A and B from that:

    class A:
        x = 123
        def __init__(self):
            self.f()
        def f(self):
            print("original function", self.x)
    
    def g(self):
        print("replacement function", self.x)
    
    A_ = A
    class A(A_):
        pass
    class B(A_):
        pass
    B.x = 456
    B.f = g
    
    a = A()
    b = B()
    

    Now, isinstance(b, A) will be False and changes to class attributes of A will not propagate to B.