121

Is there a copy constructor in python ? If not what would I do to achieve something similar ?

The situation is that I am using a library and I have extended one of the classes there with extra functionality and I want to be able to convert the objects I get from the library to instances of my own class.

martineau
  • 119,623
  • 25
  • 170
  • 301
Zitrax
  • 19,036
  • 20
  • 88
  • 110
  • You might be interested in this question [disclaimer: I was the one who asked it]: http://stackoverflow.com/questions/990758/reclassing-an-instance-in-python – balpha Aug 06 '09 at 20:22
  • Be careful. The warnings posted by some of the answerers are not to be underestimated. – balpha Aug 06 '09 at 20:35
  • To it does not look very readable, I am probably changing my code from using inheritance to just encapsulate the other object instead. – Zitrax Aug 06 '09 at 20:46

8 Answers8

85

I think you want the copy module

import copy

x = copy.copy(y)        # make a shallow copy of y
x = copy.deepcopy(y)    # make a deep copy of y

you can control copying in much the same way as you control pickle.

Tom Dunham
  • 5,779
  • 2
  • 30
  • 27
  • 14
    `deepcopy` works well for doing this external to the class definition. But @Zitrax wants to do this within his class definition so that the new instance inherits the attributes (data) from an object of a different (parent) type (class). – hobs Feb 16 '16 at 18:09
42

In python the copy constructor can be defined using default arguments. Lets say you want the normal constructor to run the function non_copy_constructor(self) and the copy constructor should run copy_constructor(self, orig). Then you can do the following:

class Foo:
    def __init__(self, orig=None):
        if orig is None:
            self.non_copy_constructor()
        else:
            self.copy_constructor(orig)
    def non_copy_constructor(self):
        # do the non-copy constructor stuff
    def copy_constructor(self, orig):
        # do the copy constructor

a=Foo()  # this will call the non-copy constructor
b=Foo(a) # this will call the copy constructor
meisterluk
  • 804
  • 10
  • 19
qwerty9967
  • 732
  • 5
  • 14
26

A simple example of my usual implementation of a copy constructor:

import copy

class Foo:

  def __init__(self, data):
    self._data = data

  @classmethod
  def from_foo(cls, class_instance):
    data = copy.deepcopy(class_instance._data) # if deepcopy is necessary
    return cls(data)
Godsmith
  • 2,492
  • 30
  • 26
  • 2
    Nice. This would work like `Foo.from_foo(foo)`. A further refinement would be to make this work for `Foo(foo)`, which is what the OP @Zitrax probably wants. – hobs Feb 16 '16 at 18:53
  • 2
    The reasons I don't like this is because 1. It forces `__init__` to take `data` as input, and is not flexible for more types 2. It doesn't allow overloading 3. `__init__` is public, which you may not want if you have other constructor types. – Gulzar Mar 11 '21 at 13:47
14

For your situation, I would suggest writing a class method (or it could be a static method or a separate function) that takes as an argument an instance of the library's class and returns an instance of your class with all applicable attributes copied over.

David Z
  • 128,184
  • 27
  • 255
  • 279
8

Building on @Godsmith's train of thought and addressing @Zitrax's need (I think) to do the data copy for all attributes within the constructor:

class ConfusionMatrix(pd.DataFrame):
    def __init__(self, df, *args, **kwargs):
        try:
            # Check if `df` looks like a `ConfusionMatrix`
            # Could check `isinstance(df, ConfusionMatrix)`
            # But might miss some "ConfusionMatrix-elligible" `DataFrame`s
            assert((df.columns == df.index).all())
            assert(df.values.dtype == int)
            self.construct_copy(df, *args, **kwargs)
            return
        except (AssertionError, AttributeError, ValueError):
            pass
        # df is just data, so continue with normal constructor here ...

    def construct_copy(self, other, *args, **kwargs):
        # construct a parent DataFrame instance
        parent_type = super(ConfusionMatrix, self)
        parent_type.__init__(other)
        for k, v in other.__dict__.iteritems():
            if hasattr(parent_type, k) and hasattr(self, k) and getattr(parent_type, k) == getattr(self, k):
                continue
            setattr(self, k, deepcopy(v))

This ConfusionMatrix class inherits a pandas.DataFrame and adds a ton of other attributes and methods that need to be recomputed unless the other matrix data can be copied over. Searching for a solution is how I found this question.

Community
  • 1
  • 1
hobs
  • 18,473
  • 10
  • 83
  • 106
  • 2
    Note that this approach differs from copy constructors of many other languages in that it won't call copy-constructors of attributes. – TLW Sep 03 '18 at 01:55
  • Also note if `v` is a numpy array or derivative, comparison with `==` will raise an error: `ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()`. If you have attributes that may be numpy array-like, a non-failing comparison (that also works with regular attributes like ints, strs, etc.) is `np.array_equal` – ntjess Apr 11 '21 at 03:49
3

I have a similar situation differing in that the new class only needs to copy attributes. Thus using @Dunham's idea and adding some specificity to @meisterluk's suggestion, @meisterluk's "copy_constructor" method could be:

from copy import deepcopy
class Foo(object):
    def __init__(self, myOne=1, other=None):
    self.two = 2
    if other <> None:
        assert isinstance(other, Foo), "can only copy instances of Foo"
        self.__dict__ = deepcopy(other.__dict__)
    self.one = myOne

def __repr__(self):
    out = ''
    for k,v in self.__dict__.items():
        out += '{:>4s}: {}, {}\n'.format(k,v.__class__,v)
    return out

def bar(self):
    pass

foo1 = Foo()
foo2 = Foo('one', foo1)

print '\nfoo1\n',foo1
print '\nfoo2\n',foo2

The output:

foo1
 two: <type 'int'>, 2
 one: <type 'int'>, 1


foo2
 two: <type 'int'>, 2
 one: <type 'str'>, one
upandacross
  • 417
  • 4
  • 7
2

The following solution probably repeats some of the previous ones in a simple form. I don't know how it is "pythocally" right, but it works and was quite convenient in the certain case I used it.

class Entity:
    def __init__(self, code=None, name=None, attrs=None):
        self.code = code
        self.name = name
        self.attrs = {} if attrs is None else attrs


    def copy(self, attrs=None):
        new_attrs = {k: v.copy() for k, v in self.attrs.items()} if attrs is None else attrs
        return Entity(code=self.code, name=self.name, attrs=new_attrs)

Usage:

new_entity = entity.copy()

This is a more complicated version that allows to interfere in the copying process. I used it in only one place. Also note that objects contained in self.attrs also have such kind of "copying constructor".

This solution is not generic but is very simple and provides quite much control.

Nick Legend
  • 789
  • 1
  • 7
  • 21
0

you can achieve like this code without using any copy module Python dosen't support method overloding so we can not make copy constructor ##

class student():
    name: str
    age: int

    def __init__(self, other=None):
        if other != None and isinstance(other, student):
            self.name = other.name
            self.age = other.age
        elif not(isinstance(other,student)) and other!=None:
            raise TypeError
    def printInfo(s):
        print(s.name, s.age)
  • That doesn't extend a base class though. Initializing an object from a copy constructor means that you can have a sublcass B of class A being initialized from an instance of class A. Class B in this case would have all methods/properties of A, plus its own, and will be initialized from an existing instance of A. – V13 Sep 24 '22 at 16:30
  • This example only for copy not for deep copy. In copy we just copy properties of object not whole object. – Mehul Talpada Sep 25 '22 at 17:14