Copy constructor in python?
Question:
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.
Answers:
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.
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.
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
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)
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.
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 'nfoo1n',foo1
print 'nfoo2n',foo2
The output:
foo1
two: <type 'int'>, 2
one: <type 'int'>, 1
foo2
two: <type 'int'>, 2
one: <type 'str'>, one
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.
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)
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.
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.
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.
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
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)
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.
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 'nfoo1n',foo1
print 'nfoo2n',foo2
The output:
foo1
two: <type 'int'>, 2
one: <type 'int'>, 1
foo2
two: <type 'int'>, 2
one: <type 'str'>, one
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.
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)