Search code examples
pythonoopsubclass

Subclass-specific attributes in superclass __init__()


I had a look around but couldn't find any answers. I have a slight issue - I have an abstract base class with a few abstract methods but also with several methods that are generic to all subclasses. Yet, in order to use these methods, I need to pass an subclass-specific attribute. This works fine, but I, of course, get warnings that the base class doesn't have the specific attribute:

Unresolved attribute reference 'c' for class 'Foo'

Let's assume I have this code:

from abc import ABC

class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def do_stuff(self):
        if hasattr(self, 'c'):
            return self.a * self.c
        elif hasattr(self, 'd'):
            return self.a + self.d


class Bar(Foo):
    def __init__(self, a, b, c):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.c = c
        
        self.some_dict = {}

    def get_value_from_dict(self):
        return self.some_dict[self.d]

class Baz(Foo):
    def __init__(self, a, b, d):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.d = d

So, Foo is an abstract base class so it's never going to be called by itself but of course it's not nice to have these warnings. Yet if I add attribute c to the base class with a value of None, this results in an error because when subclass calls superclass' init, the value gets overwritten:

class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.c = None

If I change base class' init as shown above and then instantiate class Bar and call get_value_from_dict() I will get a KeyError, otherwise if I keep things as in the original example, then all works fine:

b = Bar(1, 2, 3)
b.do_stuff()
b.get_value_from_dict()

EDIT: This is the actual code that I'm working with. This is what the do_stuff method in my example was meant to represent. Here self.component is a subclass-specific attribute and this generic method replaces erroneous values with a placeholder value.

There are several other generic methods in the base class that use self.component in a similar fashion.

class VariableImputer(ABC):
    def __init__(self, data: pd.DataFrame, deposit: str, output_loc: Optional[str] = None) -> None:
        self.data = data
        self.deposit = deposit
        self.output_loc = output_loc
        self.err_index: np.ndarray = np.full(self.data.shape[0], True)

    def _replace_erroneous_values(self):
        """
        Replace calculated component values with -99 for all rows indices of
        which are in self.err_index.
        """
        err_data = np.where(~self.err_index)[0]
        self.data.loc[err_data, self.component] = -99

class PopulateValue(VariableImputer):

    def __init__(self, data: pd.DataFrame, deposit: str, output_loc: Optional[str] = None):
        super().__init__(data=data, deposit=deposit, output_loc=output_loc)

        self.data = data
        self.deposit = deposit
        self.output_loc = output_loc

        self.component = ['porosity', 'sg']

But warnings are still there. What is the proper way to handle this situation?


Solution

  • if you don't have some attr in Foo. you still try to call it. the IDE will warn it.

    there are two options:

    1. turn off IDE lint check...(not good one.)
    2. declare these attr as abstract attr.
    pytho3 below
    
    from abc import ABC, abstractmethod
    class Foo(ABC):
        @property
        @abstractmethod
        def c(self):
            pass