Python : How to "merge" two class

Question:

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 settingsBureaumerge.py", line 22, in <module>
    print instb.getattB()
  File "D:Documents and settingsBureaumerge.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.

Asked By: user1062526

||

Answers:

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:

  1. a is initialized outside the constructor in A
  2. 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()
Answered By: ewok

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

Answered By: KevinDTimm

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.

Answered By: showns

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 settingsBureaumerge2.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.

Answered By: user1062526

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
Answered By: ThorSummoner

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
Answered By: Soubhik Biswas

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')))
Answered By: Andriy Ivaneyko

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`
Answered By: sixtyfourer

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}")
Answered By: masher
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.