Search code examples
python-3.xoopdesign-patternsabstract-class

Using abstract class with overridden constructor in Python


I am attempting to use the template method pattern with abc in Python to create an extendible base class. Primarily, I need the constructor of one of the derived classes to implement a hook method that is called from the base's constructor. Essentially, all base classes of MyBase will have attr1 and attr2, but only ChildOne needs the attributes to be validated. As such I do not want to move the initialization to the child classes as that would be clear repetition (the logic here is trivial, but in a real world application, obviously it can get much more complicated). This is what I have in the base class:

import abc

class MyBase(abc.ABC):
    @abc.abstractmethod
    def __init__(self, attr1, attr2):
        # this is only implemented in one child class
        MyBase.validate_attrs(attr1, attr2)
        self.attr1 = attr1
        self.attr2 = attr2

    @staticmethod
    @abc.abstractmethod
    def validate_attrs(attr1, attr2):
        """ In Child1, raise exception if attr1 or attr2 are invalid """
        pass

My code for the first child class looks like this:

class ChildOne(MyBase):
    def __init__(self, attr1, attr2, attr3):
        # this class will implement validate_attrs, and logic needs to be called here
        super().__init__(attr1, attr2)
        self.attr3 = attr3

    # Override default from MyBase
    @staticmethod
    def validate_attrs(attr1, attr2):
        if attr1 < attr2:
            raise ValueError("attr1 is too small!")  # this is just stand in logic to keep it brief

However, when I instantiate ChildOne with this code

c1 = ChildOne(1, 2, 3)

the default pass is what is used. I know this because no ValueError is raised, even though 1(attr1) is less than 2(attr2). I suppose this is to be expected, as I specifically call MyBase.validate_attrs. I tried using a class method, but I get the same result. Logically, it seems like I am trying to call the child class's logic from the parent class, which obviously goes against everything anybody's ever learned in OO class. How can I use the template method pattern to achieve this functionality using a hook method in the parent class so that super() runs the implemented version in the child class?


Solution

  • Using MyBase.validate_attrs explicitly says to use MyBase's version. But you don't have to; self is going to be an instance of the actual child class, so you can just change it to:

    self.validate_attrs(attr1, attr2)
    

    and it will invoke the first implementation found, beginning the search at the child class that's actually being initialized. No need to make it a classmethod, staticmethods in Python can be invoked directly on instances (the decorator ensures they don't pass either self or cls implicitly even when invoked on an instance).