0

I have two parent classes that share method names. I'd like to subclass them and reassign their methods under different names to the subclass. This is made complicated because these methods use other methods with shared names as well. Contrived but minimal example:

class Foo1():
    def __init__(self):
        super().__init__()

    def print(self):
        print(self.get())

    def get(self):
        return 1


class Foo2():
    def __init__(self):
        super().__init__()

    def print(self):
        print(self.get())

    def get(self):
        return 2


class Foo(Foo1, Foo2):
    def __init__(self):
        super().__init__()

        self.print1 = super().print
        self.print2 = super(Foo1, self).print


foo = Foo()
foo.print1()
>>> 1
foo.print2()
>>> 1

In the example above, foo.print1() prints 1 as expected, but foo.print2() prints 1 instead of 2. I would like to structure Foo such that it calls Foo2's print and get methods and thus prints 2.

I understand this is because the name "self" actually points to Foo(), and so the methods "print" and "get" are pointing to Foo1's methods due to Foo1 being first in the MRO.

For my use case, it doesn't even make sense for Foo() to have "print" and "get" methods, only "print1" and "print2". This makes me suspect that I'm going about it all wrong. Is there a better way to construct a subclass that correctly inherits these methods from Foo1 and Foo2?

Nevermore
  • 318
  • 2
  • 11

2 Answers2

1

Have your class wrap Foo1 and Foo2 instances rather than using multiple inheritance.

class Foo:
    def __init__(self):
        foo1 = Foo1()
        foo2 = Foo2()

        self.print1 = foo1.print
        self.print2 = foo2.print

        self.get1 = foo1.get
        self.get2 = foo2.get
Samwise
  • 68,105
  • 3
  • 30
  • 44
  • Would this be considering a composition, as you alluded to in your initial comment? I hadn't heard of that before but it seems straightforward enough. – Nevermore Nov 05 '21 at 18:21
  • 1
    I wouldn't make `print1` et al. instance attributes. Just define `def print1(self): self.foo1.print()`, etc. – chepner Nov 05 '21 at 18:23
  • @chepner that would involve making foo1 and foo2 instance attributes instead right? What would be the advantage of doing so? – Nevermore Nov 05 '21 at 18:26
  • @Nevermore you've never heard the term, perhaps, but you *almost certainly use it every time you create a class*. – juanpa.arrivillaga Nov 05 '21 at 18:26
  • @Samwise I implemented your way but just without the self.get1 and self.get2 methods since they don't need to be exposed for my use-case. Thank you! – Nevermore Nov 05 '21 at 18:30
0

One option is to use composition, where your new class instance is just a wrapper around two instances of the original classes. Your methods will delegate to methods of the appropriate instances.

class Foo:
    def __init__(self):
        self.foo1 = Foo1()
        self.foo2 = Foo2()

    def print1(self):
        return self.foo1.print()

    def print2(self):
        return self.foo2.print()

    # etc

You might also be able to perform the composition at the class level, rather than the instance level. This will only work if the function signatures of Foo1 and Foo2 methods are compatible with the desired signature for the Foo methods.

class Foo:
    print1 = Foo1.print
    print2 = Foo2.print
    get1 = Foo1.get
    get2 = Foo2.get

In either case, you'll have to define Foo.__init__ to accept any necessary arguments for Foo1.__init__ and Foo2.__init__, and ensure they are passed to the correct one.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • This wouldn't work because get1 and get2 are never called by anything. – Nevermore Nov 05 '21 at 18:27
  • They are just class attributes that will be accessed once you do something like `Foo().get1()`; it doesn't really matter *where* the function was originally defined. – chepner Nov 05 '21 at 18:30