Search code examples
pythonclassobjectinheritancedata-class

python object building using multi-inheritance


I want to build an object dynamically which allow use to mix the class properties in whichever way they like base on multiple inheritance. This is the expected behaviour. These classes are dataclasses so there won't be many methods in them, mostly data properties.

class Foo():
    def bar(self, x):
            return x

class FooA(Foo):
    def bar(self, x):
        p = super().bar(x)
        p += __class__.__name__
        return p

class FooB(Foo):
    def bar(self, x):
        p = super().bar(x)
        p += __class__.__name__
        return p

class FooC(FooA, FooB):
    def bar(self, x):
        p = super().bar(x)
        p += __class__.__name__
        return p

f = FooC()
f.bar('S') # SFooBFooAFooC

However this code violate the DRY principle in broad daylight, hence I want to avoid the bar method completely, if there is no special operations in the current class.

Ideally I want something like

@bar_wrapper
class FooA(Foo):
    pass

# OR

class FooA(Foo):
    __metaclass__ = BarBase

Instead of this full implementation

class FooA(Foo):
    def bar(self, x):
        p = super().bar(x)
        p += __class__.__name__
        return p

Essentially is there a way that I extract the middle layer class information in a multi-level inheritance class through a decorator or metaclass (the two options that I can think of)? Anyone has any idea on how to do this?


Solution

  • Write a class decorator that adds the bar method to the class:

    def bar_wrapper(cls):
        def bar(self, x):
            p = super(cls, self).bar(x)
            p += cls.__name__
            return p
    
        bar.__module__ = cls.__module__
        bar.__qualname__ = '{}.{}'.format(cls.__qualname__, bar.__name__)
    
        cls.bar = bar
        return cls
    
    class Foo():
        def bar(self, x):
            return x
    
    @bar_wrapper
    class FooA(Foo):
        pass
    
    @bar_wrapper
    class FooB(Foo):
        pass
    
    @bar_wrapper
    class FooC(FooA, FooB):
        pass
    
    f = FooC()
    print(f.bar('S')) # SFooBFooAFooC