Home > Blockchain >  Python get a copy of current class in an inheritable way
Python get a copy of current class in an inheritable way

Time:03-02

I have the following setup:

from copy import deepcopy

class A(object):
    def __init__(self, *args):
        assert all([type(a) is int for a in args])
        self.data = list(args)

    def copy(self):
        cp = A()
        cp.data = self.data.copy()
        cp.__class__ = self.__class__
        return cp

class B(A):
    def do_extra(self):
        print('does extra stuff')


b = B()
l = b.copy()


print(l)
# -> <__main__.B object at 0x10f3b9070>

l.do_extra()
# -> does extra stuff

I want B to inherit the copy() method of A and return a deep copy of the B class, but I'm not sure how to refer to the "constructor" in a way that won't break if I add extra initialisation to B. I'm aware of copy.deepcopy, which is perhaps a better solution, but I'm still not sure if it is the canonical method since it seems to cause issues with complex inheritance structures.

CodePudding user response:

You should be using the deepcopy method which will work without any extra messing around:

import copy

class A(object):
    def __init__(self):
        self.data = 1

class B(A):
    def do_extra(self):
        print('does extra stuff')

b = B()
b_copy = copy.deepcopy(b)

b_copy.do_extra() # works

The other question you linked has issues due to pickling the classes so you probably don't need to do that here based on your example.

CodePudding user response:

While copy.deepcopy is almost certainly the correct way to do this (so please, use QuantumMecha's answer), you can determine the class of the derived type via self.__class__ (or slightly more Pythonically if you can guarantee Python 3 or new-style classes in Python 2, type(self)), and it is a callable. For the specific case shown here, you could implement it as:

def copy(self):
    cp = type(self)()
    cp.data = self.data.copy()
    return cp

and that would correctly create an instance of B if you called .copy() on an instance of B. It won't work if B's initializer violates the Liskov Substitution Principle (if it does, B would have to override copy itself to do "the right thing", whatever that may be), but as long as the arguments to the initializers are compatible, it would work.

Alternatively, if you want to create a 100% barebones instance and populate it entirely manually (perhaps because original initialization work doesn't make sense or would be incorrect in the context of copying), you could do (for classes that don't override __new__):

def copy(self):
    cls = type(self)
    cp = cls.__new__(cls)  # Makes new instance without invoking __init__
    # *All* initialization must be done here, so don't forget anything
    cp.data = self.data.copy()
    return cp
  • Related