3

I have a child class with two parents with identical function names.

I need to decide (based on type of object being created) within the constructor (__init__) that which parent class' functions need to be called by the object of child. Is there a way to dynamically link/switch default function calls to that of one of the parents. Sample code given below. I don't want to use container-ship of objects as its very difficult to maintain and refactor.

class Square: 
 def perform(self, num):
     print(num * num)

class Cube: 
 def perform(self, num):
     print(num * num * num)

class Exponent(Square, Cube):
 def __init__(self, type):
    if type == 'square':
        # inherit default functions from square <--- ??
    else:
        # inherit default functions from cube <--- ??

    square = Square()  # <-- i don't want to do this
    cube = Cube()      # <-- i don't want to do this

Exponent('cube').perform(2)    # --> should print '8'
Exponent('square').perform(2)  # --> should print '4'

Exponent('cube').cube.perform(2)      # <-- i don't want to do this
Exponent('square').square.perform(2)  # <-- i don't want to do this

One way is given below but involves duplicating all parent class functions which is way too much overkill:

class a:
    def x (self):
        print('a')

class b:
    def x (self):
        print('b')

class c(a,b):
    def __init__(self, type_):
        if type_ == 'a':
            self.ref = a
        else:
            self.ref = b

    def x(self):
        self.ref.x(self)


c('a').x()
c('b').x()
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ulysses
  • 5,616
  • 7
  • 48
  • 84
  • 2
    That inheritance hierarchy doesn't make sense. `class Exponent(Square, Cube)` means that any exponent is both a square and a cube. – user2357112 Jun 25 '19 at 04:11
  • 1
    Why are you trying to use a subclass for this at all? – user2357112 Jun 25 '19 at 04:11
  • This is just a sample code - I need to child to decide a default parent dynamically based on its type and subsequently all function calls that are common between parents should be sourced from the decided parent class. @user2357112: I interpret it as `class Exponent(Square, Cube)` means that any exponent is either a square or a cube. Also, the example is just with one common call to a parent - there will be multiple such calls whose names shall be same but functionality vary based on child type – Ulysses Jun 25 '19 at 04:24
  • 1
    The way you interpret it is not the way Python interprets it. – user2357112 Jun 25 '19 at 04:25
  • 2
    The canonical understanding of such an inheritance hierarchy would be that `Exponent` is a `Square` *and* a `Cube`, not *or*. – gmds Jun 25 '19 at 04:27
  • Look, I am not saying what way python interprets or not. I just want to selectively refer to a parent. If the child is of type `a` then all functions common to both parents `a` and `b` should be called from `a` and If the child is of type `b` then all functions common to both parents `a` and `b` should be called from `b` without any additional effort. If there is a better way of implementing it please let me know and I will implement accordingly. – Ulysses Jun 25 '19 at 04:27
  • Hmm... does "all functions" include `__init__`? Because if it does, I really don't see any difference between an `a`-type child and an object that is just of the type `a`. – Imperishable Night Jun 25 '19 at 04:51
  • Look, `__init__` is the one that will help decide what parent to choose subsequently. Its like I have two hardware products with several similar features but minor nuances in calculations but same set of parameters. I want to provide a single class with a parameter that marks the difference. This will make Life very easy for the person coding the interface, the person calling the interface and the person extending / maintaining the interface. – Ulysses Jun 25 '19 at 04:55
  • @juanpa.arrivillaga - i feel you are right - this is a proxy object that delegates to other objects. But it will make things easy in my case. This gives me an idea of implementation with minimal additional effort and will post soon. – Ulysses Jun 25 '19 at 05:03
  • 1
    @barry_allen yes, I've gone ahead and posted one simple approach. – juanpa.arrivillaga Jun 25 '19 at 05:05
  • Metaclass time! – Mad Physicist Jun 25 '19 at 05:12

2 Answers2

6

Python offers a lot of flexibility, but you're working against the class mechanisms the language offers. there is no way to do this "with out any additional effort" since you will be implementing your own mechanisms on top of what the language offers. Honestly, just forget about using multiple inheritance, because inheritance is not what you are describing, you want a proxy object that delegates to the appropriate object. Depending on your specific circumstances, this could look different, but this will get you going:

In [1]: class A:
   ...:     def x (self):
   ...:         print('a')
   ...:
   ...: class B:
   ...:     def x (self):
   ...:         print('b')
   ...:
   ...: class C:
   ...:     def __init__(self, type_, *args, **kwargs):
   ...:         self.__wrapped = type_(*args, **kwargs)
   ...:     def __getattr__(self, attr):
   ...:         return getattr(self.__wrapped, attr)
   ...:
   ...:

In [2]: C(A).x()
a

In [3]: C(B).x()
b

Note, the way C.__init__ is implemented, everything after the first argument is passed to the delegated type's constructor.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • This is a really good way to proxy another class, but I'd note that it won't support special methods defined on the target (like `__add__`). Those methods use special lookup logic, so `__getattr__` can't handle them for you. – Blckknght Jun 25 '19 at 05:21
  • @Blckknght yes, definitely, that is a shortcoming. – juanpa.arrivillaga Jun 25 '19 at 05:35
  • @Blckknght, juanpa - I am good with this for now - it solves my current problems – Ulysses Jun 25 '19 at 05:38
3

I will take a jab at this. Would a factory function instead of a child class satisfy your need? If it doesn't, comment what you want and I might be able to find a way.

class Square: 
    def perform(self, num):
        print(num * num)

class Cube: 
    def perform(self, num):
        print(num * num * num)

def Exponent(type):
    if type == 'square':
        return Square()
    else:
        return Cube()

Exponent('cube').perform(2)
Exponent('square').perform(2)

If you want everything returned by Exponent to have some common behavior, then you can define another class for that.

class Square: 
    def perform(self, num):
        print(num * num)

class Cube: 
    def perform(self, num):
        print(num * num * num)

class ExponentType:
    def child_perform(self, num):
        print(num ** 4)

class ExponentSquare(ExponentType, Square): pass
class ExponentCube(ExponentType, Cube): pass

def Exponent(type):
    if type == 'square':
        return ExponentSquare()
    else:
        return ExponentCube()

Exponent('cube').perform(2)
Exponent('square').perform(2)
Exponent('cube').child_perform(2)
Exponent('square').child_perform(2)
Imperishable Night
  • 1,503
  • 9
  • 19
  • Yes, an object factory is another more fitting pattern than multiple inheritance. – juanpa.arrivillaga Jun 25 '19 at 05:05
  • This does to quite an extent but `Exponent` also needs to contain some info. I think I will look further into it and see if I can make a more useful cocktail of sorts. – Ulysses Jun 25 '19 at 05:09