Python : How to "merge" two class - python

I want to add some attributes and methods into various class. The methods and attributes that I have to add are the same but not the class to assign them, so I want to construct a class who assign new methods and attributes for a class given in argument.
I try this but it's not working:
(I know that is a very wrong way to try to assign something to self, it's just to show what I want to do)
class A:
def __init__(self):
self.a = 'a'
def getattA(self):
return self.a
class B:
def __init__(self, parent) :
self = parent
# This is working :
print self.getattA()
def getattB(self):
return self.getattA()
insta = A()
instb = B(insta)
# This is not working :
print instb.getattB()
The result is :
a
Traceback (most recent call last):
File "D:\Documents and settings\Bureau\merge.py", line 22, in <module>
print instb.getattB()
File "D:\Documents and settings\Bureau\merge.py", line 16, in getattB
return self.getattA()
AttributeError: B instance has no attribute 'getattA'
And I expected to got 'a' for the call of instb.gettattB()
To resume I want to inherit class B from class A giving class A in argument of class B because my class B will be a subclass of various class, not always A.

The Best answer is in the comments, it was useful for me so I decided to show it in an answer (thank to sr2222):
The way to dynamicaly declare inherance in Python is the type() built-in function.
For my example :
class A(object) :
def __init__(self, args):
self.a = 'a'
self.args = args
def getattA(self):
return self.a, self.args
class B(object) :
b = 'b'
def __init__(self, args) :
self.b_init = args
def getattB(self):
return self.b
C = type('C', (A,B), dict(c='c'))
instc = C('args')
print 'attributes :', instc.a, instc.args, instc.b, instc.c
print 'methodes :', instc.getattA(), instc.getattB()
print instc.b_init
The code return :
attributes : a args b c
methodes : ('a', 'args') b
Traceback (most recent call last):
File "D:\Documents and settings\Bureau\merge2.py", line 24, in <module>
print instc.b_init
AttributeError: 'C' object has no attribute 'b_init'
My class C inerhite attributes and methods of class A and class B and we add c attribute. With the instanciation of C (instc = C('args')) The init for A is call but not for B.
Very useful for me because I have to add some attributes and methodes (the same) on different class.

I was having trouble with calling different constructors, using super doesn't necessarily make sense in a case like this, I opted to inherit and call each constructor on the current object manually:
class Foo(object):
def __init__(self, foonum):
super(Foo, self).__init__()
self.foonum = foonum
class Bar(object):
def __init__(self, barnum):
super(Bar, self).__init__()
self.barnum = barnum
class DiamondProblem(Foo, Bar):
# Arg order don't matter, since we call the `__init__`'s ourself.
def __init__(self, barnum, mynum, foonum):
Foo.__init__(self, foonum)
Bar.__init__(self, barnum)
self.mynum = mynum

How about this?
class A:
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B:
def __init__(self, parent) :
self.parent = parent
def __getattr__(self, attr):
return getattr(self.parent, attr)
def getattB(self):
return self.parent.getatt()
insta = A()
instb = B(insta)
print instb.getattB()
print instb.getatt()
But method in class A can not access attr in class B.
Another way:
import functools
class A:
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B:
def __init__(self, parent):
for attr, val in parent.__dict__.iteritems():
if attr.startswith("__"): continue
self.__dict__[attr] = val
for attr, val in parent.__class__.__dict__.iteritems():
if attr.startswith("__"): continue
if not callable(val): continue
self.__dict__[attr] = functools.partial(val, self)
def getattB(self):
return self.getatt()
insta = A()
instb = B(insta)
print instb.__dict__
print instb.getattB()
print instb.getatt()
Slow with init but call fast.

Since B is not a subclass of A, there is no path in B to getatt() in A

I guess i have a easier method
class fruit1:
def __init__(self):
self.name = "apple"
self.color = "blue"
class fruit2:
def __init__(self):
self.name = "banana"
self.size = 100
def merge(ob1, ob2):
ob1.__dict__.update(ob2.__dict__)
return ob1
f1 = fruit1()
f2 = fruit2()
fruit = merge(f1, f2)
print("name:",fruit.name," color:",fruit.color, " size:",fruit.size)
#output: name: banana color: blue size: 100

I'm not certain what you are trying to do, but the code below is giving my the output I think you are expecting. notice:
a is initialized outside the constructor in A
B is declared as a subclass of A
Code:
class A:
a='' #Initialize a
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B(A): #Declare B as subclass
def __init__(self, parent) :
self = parent
print self.getatt()
def getattB(self):
return self.getatt()
insta = A()
instb = B(insta)
print instb.getattB()

Helper function below conducts the merge of the dataclass instances, the attributes orders is derived from *args order:
from dataclasses import dataclass
#dataclass
class A:
foo: str
bar: str
def merge_dataclasses(*args):
if len({e.__class__.__name__ for e in args}) > 1:
raise NotImplementedError('Merge of non-homogeneous entries no allowed.')
data = {}
for entry in args[::-1]:
data.update(vars(entry))
return entry.__class__(**data)
print(merge_dataclasses(A(foo='f', bar='bar'), A(foo='b_foo', bar='b_bar')))

One easy way to merge two or more classes is through the tool set dyndesign:
from dyndesign import mergeclasses
class Base:
def __init__(self, init_value):
self.param = init_value
def m1(self):
print(f"Method `m1` of class `Base`, and {self.param=}")
def m2(self):
print(f"Method `m2` of class `Base`")
class Ext:
def m1(self):
print(f"Method `m1` of class `Ext`, and {self.param=}")
MergedClass = mergeclasses(Base, Ext)
merged_instance = MergedClass("INITIAL VALUE")
merged_instance.m1()
# Method `m1` of class `Ext`, and self.param='INITIAL VALUE'
merged_instance.m2()
# Method `m2` of class `Base`

Emphasizing ThorSummoner's's answer and Hong's comment; this method appears to be cleaner than the excepted answer. Notice Hong's use of super().init(self) in all but the last object added to the merge class.
class Foo(object):
def __init__(self, foonum):
super(Foo, self).__init__(self)
self.foonum = foonum
class Bar(object):
def __init__(self, barnum):
super(Bar, self).__init__(self)
self.barnum = barnum
class Oops(object):
def __init__(self, oopsnum):
super(Oops, self).__init__()
self.oopsnum = oopsnum
class DiamondProblem(Foo, Bar, Oops):
def __init__(self, mynum, foonum, barnum, oopsnum):
Foo.__init__(self, foonum)
Bar.__init__(self, barnum)
Oops.__init__(self, oopsnum)
self.mynum = mynum
def main():
dia = DiamondProblem(1, 10, 20, 30)
print(f"mynum: {dia.mynum}")
print(f"foonum: {dia.foonum}")
print(f"barnum: {dia.barnum}")
print(f"oopsnum: {dia.oopsnum}")

Related

Inheritance - Calling methods within methods

I know the super() function allows you to call a parent method from a child class. So in this example:
class Exam():
def __init__(self, a):
self.a = a
def show(self):
print("This is from parent method")
class Quiz(Exam):
def __init__(self, b, a):
super().__init__(a)
self.b = b
def show(self):
print("This is from child method")
super().show()
q = Quiz('b', 'a')
q.show()
>>> 'This is from child method'
>>> 'This is from parent method'
What if I added another method called get_score to both the parent and child class like here:
class Exam():
def __init__(self, a):
self.a = a
def show(self):
print("This is from parent method")
print (self.get_score())
def get_score(self):
return self.a
class Quiz(Exam):
def __init__(self, b, a):
super().__init__(a)
self.b = b
def show(self):
print("This is from child method")
super().show()
print (self.get_score())
def get_score(self):
return self.b
q = Quiz('b', 'a')
q.show()
>>> 'This is from child method'
>>> 'This is from parent method'
>>> 'b'
>>> 'b'
I can understand why calling super().show() in the child class would return 'b' since I am overwriting the get_score() method.
However, is there a way to maintain the integrity of the parent class so that when I do call super().show()
I get this instead?
>>> 'This is from child method'
>>> 'This is from parent method'
>>> 'a'
>>> 'b'
Sorry in advance if this is bad design. Let me know what other alternatives I can take, even if it means I should the name of the method to avoid this kind of collision.
Use name-mangling, which will prevent the name-collisions in the subclass
:
class Exam():
def __init__(self, a):
self.a = a
def show(self):
print("This is from parent method")
print (self.__get_score())
def __get_score(self):
return self.a
get_score = __get_score
class Quiz(Exam):
def __init__(self, b, a):
super().__init__(a)
self.b = b
def show(self):
print("This is from child method")
super().show()
print (self.__get_score())
def __get_score(self):
return self.b
get_score = __get_score
q = Quiz('b', 'a')
q.show()

Most elegant way to define simple abstract attributes

I'm trying to implement an abstract class with attributes and I can't get how to define it simply.
I just want to define the attribute name to constrain child classes to have it but I don't want to copy/paste getters & setters in every classes that inherit my abstract class.
Here are solutions I found but not very elegant in my opinion:
Maybe the most efficient and robust way, but quite ugly and redundant. We have to put 'def a(): pass', in each child class
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC1(AbstractC):
def __init__(self, name):
self.a = name
def a(self):
pass
class ConcreteC2(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC3(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
Quite the same, but uglier imo
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC1(AbstractC):
a = None
def __init__(self, name):
self.a = name
class ConcreteC2(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC3(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
Most compact way, but not robust. No error if 'a' is missing
class AbstractC(ABC):
#abstractmethod
def __init__(self, val):
self.a = val
class ConcreteC1(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC2(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # no error !
So is there a way to get an elegant, robust and compact abstract class with abstract attribute ? Or am I trying to get something impossible ? I was thinking about something close to that :
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC(AbstractC):
def __init__(self, name):
self.a = name
If there is no such solution, what is the best one ?
You could misuse namedtuples for fancy inheritance
from collections import namedtuple
BaseAttributes = namedtuple('base', ['attr1', 'attr2'])
print(BaseAttributes('one', 2))
class SomethingElse(BaseAttributes):
def method(self):
return 3
blubb = SomethingElse('A', 5)
blubb.method()
but imho your last proposal(s) makes sense if you raise NotImplementedError, e.g.:
class AbstractC(ABC):
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, name, *args, **kwargs):
super().__init__(*args, **kwargs)
self.a = name
Maybe this will help. I made a class which inherits from ABC. It defines the method __init_subclass__ that is invoked after a new subclass is created. It does the next: For each abstract property declared, search the same method in the subclass. If it exists (its a function object) convert it to a property and replace it in the subclass dictionary.
from abc import ABC, abstractmethod
class Foo(ABC):
def __init_subclass__(cls):
super().__init_subclass__()
###### This is the new part. I explain it at the end of the answer
for name, value in attrs.items():
if name not in cls.__dict__:
setattr(cls, name, property(lambda *args, **kwargs: value))
######
# Iterate throught all abstract methods on the class
for name in Foo.__abstractmethods__:
absmethod = Foo.__dict__[name]
# Check if the abstract method is a property
if not isinstance(absmethod, property):
continue
# Check if there is a method defined in the subclass with the same name
if name not in cls.__dict__ or not callable(cls.__dict__[name]):
continue
method = cls.__dict__[name]
# If the method is not already a property, we decorate it automatically...
if not isinstance(method, property):
setattr(cls, name, property(method))
#property
#abstractmethod
def a(self):
return 1
Now define a subclass and test it:
class Bar(Foo):
def __init__(self):
pass
def a(self):
return 2
#property
def b(self):
return 3
obj = Bar()
print(obj.a)
print(obj.b)
Output will be:
2
3
The next code will raise an error, because not all abstract methods are implemented:
class Qux(Foo):
pass
EDIT:
Now you can also do:
class Bar(Foo, a=1):
pass
print(Bar().a) # 1
There's still a problem. If i choose the implementation that raise an error, i have to add #property to the method or i can call ConcreteC().a even if a is not set and it will not raise the error:
class AbstractC(ABC):
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, val):
super().__init__()
self.poney = val
In [3]: ConcreteC('foobar').a
Out[3]: <bound method AbstractC.a of <__main__.ConcreteC object at 0x7f2e1c6b0518>>
But if i add #property i get an error :
class AbstractC(ABC):
#property
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, val):
super().__init__()
self.a = val
In [4]: ConcreteC('foobar')
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-55-587237cb76e5> in <module>
----> 1 ConcreteC('foobar')
~/workspace/draft.py in __init__(self, val)
151 def __init__(self, val):
152 super().__init__()
--> 153 self.a = val
154
155
AttributeError: can't set attribute
EDIT:
Here the solution I chose:
class AbstractC(ABC):
#property
def a(self):
try:
return self._a
except AttributeError:
raise NotImplementedError('Implement _a_ method')
#a.setter
def a(self, val):
self._a = val
class ConcreteC(AbstractC):
def __init__(self, val):
self.a = val
This way I can edit 'a' very simply and if it's not definied, an exception is raised on get. I didn't know that to make a setter work, it must has the same name as the property.
In the end, what I wanted isn't an abstract attribute, but a concrete one in an abstract class.
In [1]: c = ConcreteC('foobar')
In [2]: c.a
Out[2]: 'foobar'
In [3]: c.a = 'poney'
In [4]: c.a
Out[4]: 'poney'

How to call child constructor from parent?

In inheritance, most of the time we want to create child classes that inherit from the parent, and in the process of instantiation they have to call the parent constructor. In python we use super for this, and that's great.
I want to do somewhat the opposite: I have a parent class which is a template for a number of child classes. Then I want the child classes to each have a function that allows an instance to clone itself:
class Parent(object):
def __init__(self, ctype, a):
print('This is the parent constructor')
self._ctype = ctype
self._a = a
#property
def a(self):
return self._a
#property
def ctype(self):
return self._ctype
class ChildOne(Parent):
def __init__(self, a):
super(ChildOne, self).__init__('one', a)
print('This is the child One constructor')
self.one = 1
def clone(self):
return ChildOne(self._a)
class ChildTwo(Parent):
def __init__(self, a):
super(ChildTwo, self).__init__('two', a)
print('This is the child Two constructor')
self.two = 2
def clone(self):
return ChildTwo(self._a)
Now, if I create an instance of one of the children, I can clone it:
>>> k = ChildOne(42)
>>> k.ctype
'one'
>>> l = k.clone()
>>> l.a
42
>>> l is k
False
The problem is, the clone method is repeated- and nearly identical- in both sub-classes, except I need to specify explicitly which constructor to call. Is it possible to design a clone method that I define in the parent class, that correctly inherits to the children?
This can be done with:
Code:
class Parent(object):
def clone(self):
return type(self)(self._a)
Test Code:
class Parent(object):
def __init__(self, ctype, a):
print('This is the parent constructor')
self._ctype = ctype
self._a = a
#property
def a(self):
return self._a
#property
def ctype(self):
return self._ctype
def clone(self):
return type(self)(self._a)
class ChildOne(Parent):
def __init__(self, a):
super(ChildOne, self).__init__('one', a)
print('This is the child One constructor')
self.one = 1
class ChildTwo(Parent):
def __init__(self, a):
super(ChildTwo, self).__init__('two', a)
print('This is the child Two constructor')
self.two = 2
k = ChildOne(42)
print(k.ctype)
l = k.clone()
print(l.a)
print(type(l))
Results:
This is the parent constructor
This is the child One constructor
one
This is the parent constructor
This is the child One constructor
42
<class '__main__.ChildOne'>

Newbie Debugging a Python Code

I want local variable of a class to be changed by other class. My script is similar to the following :
import datetime
b = []
class P:
def __init__(self):
self.count = 1
self.create()
def create(self):
global b
a = C(self.count)
for i in range(10):
a.print_name()
print b
class C:
def __init__(self, *data):
self.test = data[0]
#staticmethod
def print_name():
global b
b.append(datetime.datetime.now())
o = P()
How to avoid use of a global variable . On web i found use of "super " can resolve the issue . Please help in this regard .
Make C constructor to accept P instance. And call the method of P to append item to instance attribute of P object.
class P:
def __init__(self):
self.count = 1
self.items = []
self.create()
def append(self, item):
self.items.append(item)
def create(self):
a = C(self, self.count) # <-- Pass P instance (self)
for i in range(10):
a.print_name()
print self.items
class C:
def __init__(self, p_instance, *data):
self.p_instance = p_instance # <-- Save P instance to use later
self.test = data[0]
def print_name(self):
self.p_instance.append(datetime.datetime.now()) # <-- Call p instance method
You are probably looking for a class attribute. If you add b as an attribute to the C class, it can be accessed as C.b and C().b, i.e. from a reference to the class or any instance.
class C(object): # classes in py2 should inherit from object!
b = [] # b inside C definition
#classmethod
def print_name(cls):
cls.b.append(datetime.datetime.now())
class P(object):
def __init__(self):
self.count = 1
self.create()
def create(self):
a = C(self.count)
for i in range(10):
a.print_name()
print C.b # or a.b
Of course, you can also place b on P. In this case, do
def print_name():
P.b.append(datetime.datetime.now())

Accidentally calling an ovverriden method from base class's __init__

This program seems to do everything by the book, yet this issue cropped up: while a base class was being init'ed a member method was called that is overriden in the derived class and assumes that the derived class has been constructed.
Is there some best practice to protect against this?
#!/usr/bin/env python3
class A:
def __init__(self):
self.ax = 1
print(self)
def __repr__(self):
return "{} ax: {}".format(self.__class__.__name__, self.ax)
class B(A):
def __init__(self):
super().__init__()
self.bx = 10
def __repr__(self):
return super().__repr__() + " bx: {}".format(self.bx)
if __name__ == "__main__":
B()
And here's the error:
AttributeError: 'B' object has no attribute 'bx'
Generally, unless you really know what you are doing, you want to call the superclass initialization after everything your class needs to do is done. Same with this example, repr is trying to print self.bx before you initialize it. If you do
class B(A):
def __init__(self):
self.bx = 10
super().__init__()
def __repr__(self):
return super().__repr__() + " bx: {}".format(self.bx)
it works as expected
Edited:
Instead of doing computation on __init__, one idea may be to do that in a factory function/classmethod.
Example instead of doing:
class A:
def __init__(self, a, b):
self.a = a
self.b = b
self.initialize()
def initialize(self):
# do some things
Do:
class A:
def __init__(self, a, b):
self.a = a
self.b = b
#classmethod
def from_a_b(cls, a, b):
instance = cls(a, b)
instance.initialize()
return instance

Categories

Resources