0

Let there be an abstract class Animal which is extended and implemented by class Dog and class Cat.

class Animal:
  def speak(self):
    raise NotImplementedError

  ... # 50 other methods with implementation and another 40 without implementation (abstract, headers only)

class Dog(Animal):
  def speak(self):
    return "ruff"

  ... # 40 more implemented methods

class Cat(Animal):
  def speak(self):
    return "purr"

  ... # 40 more implemented methods

There is also a bunch of functionality that can turn Animals into RoboAnimals, which causes every Animal type to say "beep boop" before anything whenever they speak.

How should one implement both a class RoboDog and a class RoboCat while avoiding duplicating code? Neither Dog, Cat, or Animal can be manipulated.

I've tried using another class -- RoboAnimal -- that extends Animal in order to cover for 50 basic Animal methods, but Animal has 40 other abstract methods that need appropriate implementation (Dog/Cat). If I use composition, and simply wrap a Dog or Cat instance, I'd still have to make sure that all Animal methods called on this object behave accordingly to whether the flying animal is actually a Dog or a Cat.

Just to be clear, a RoboDog returns "beep boop ruff" on .speak() calls and otherwise behaves like a Dog, and likewise with Cat ("beep boop purr").

Can you do that in Python 3.10?

EDIT: My attempts at solving the problem:

#1

class RoboDog(Dog):
  def speak(self):
    return "beep boop " + super().speak()

class RoboCat(Cat):
  def speak(self):
    return "beep boop " + super().speak()

  # damn, I've just duplicated the code...
  # what if adding robotic functionality 
  # to animals took hundreds of lines of 
  # code, now that would be a hassle to maintain

#2

class RoboAnimal(Animal):
  def speak(self):
    return "beep boop " + super().speak()
    # oh snap, super().speak() raises an error :(

#3

class RoboAnimal(Animal):
  def __init__(self, animal_cls, ...):
    super().__init__(...)
    self.wrapped_animal = animal_cls(...)

  def speak(self):
    return "beep boop " + self.wrapped_animal.speak()
    # now this is epic, but none of the 40 abstract methods work now
4
  • 2
    Have you looked into mixins? Commented Aug 16, 2022 at 20:22
  • Can you show what you tried instead of just describing it? That would be very helpful. Commented Aug 16, 2022 at 20:32
  • @Shiva Unless I missed something in the linked post (which is possible), mixins add specific additional functionality to the base class. What a RoboAnimal needs is to use a specific implementation of a base method to override the very same method. Not sure how mixins help in this case, please do explain. Commented Aug 16, 2022 at 20:35
  • @Shiva Turns out I did misunderstand mixins! Thank you. Commented Aug 16, 2022 at 20:44

1 Answer 1

2

You can create a class that overrides only one of the methods you need, and insert it in the right order in the inheritance tree. This is called a mixin:

class Animal:
    pass

class Dog(Animal):
    def speak(self):
        return 'woof!'

class RoboAnimal:
    def speak(self):
        return 'beep boop ' + super().speak()

class RoboDog(RoboAnimal, Dog):
     pass

print(RoboDog().speak())

Notice that RoboDog.speak calls super().speak. This means that if you don't put a valid instance of Animal in the inheritance chain after it, it will crash if it is called. RoboAnimal does not need to inherit from Animal because it serves an entirely different purpose. In fact I would probably call the class something like RoboMixin to avoid confusion.

In general, the order of base classes in multiple inheritance is important. For example, in the following case, the mixin method would never be called because Dog.speak never calls super().speak:

class WrongRoboDog(Dog, RoboAnimal):
    pass
Sign up to request clarification or add additional context in comments.

4 Comments

Perfect, just what I was looking for. I was wondering whether the priorities were given like that. Thank you kind sir
@CaptainTrojan. You almost had it. You made the mixin, but did not use it. Python 3 uses C3: en.wikipedia.org/wiki/C3_linearization
I had no idea that you can use super() inside classes that do not inherit from anything. A very fine trick. Of course, now it makes no sense for a RoboAnimal to inherit from Animal at all (y)
@CaptainTrojan. super() just steps along the MRO of the class, which means that you can insert whatever you want into the inheritance tree. It's a nice little trick that lets you avoid a lot of code reuse if you do it right, and shoots you hard in the foot if you don't.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.