Using base class for all object creation

195 views Asked by At

A senior dev would like me to implement Object Oriented Programming in Python where we instantiate all object creation using the Base class. It does not sit well with me because there are abstract methods in the Base class that the Derived class has to implement. His reasoning to use the Base class only as a way to instantiate our objects is so that when we iterate through a list of our objects, we can access its variables and methods the same way. Since each derived object of the base class has more attributes instantiated than the Base class, he suggests the init function to take in *args and **kwargs as part of the arguments.

Is this a good way to go about doing it? If not, can you help suggest a better alternative?

Here's a simple example of the implementation.

import abc
class Base(metaclass = abc.ABCMeta):
    def __init__(self, reqarg1, reqarg2, **kwargs):
        self.reqarg1 = reqarg1
        self.reqarg2 = reqarg2
        self.optarg1 = kwargs.get("argFromDerivedA", 0.123)
        self.optarg2 = kwargs.get("argFromDerivedB", False)
        self.dict = self.create_dict()

    @abstractmethod
    def create_dict(self):
        pass

    def get_subset_list(self, id):
        return [item for item in self.dict.values() if item.id == id] 

    def __iter__(self):
       for item in self.dict.values():
           yield item
    raise StopIteration()


class Derived_A(Base):
    def __init__(self, regarg1, regarg2, optarg1):
        super().__init__(regarg1, regarg2, optarg1)

    def create_dict(self):
        # some implementation
        return dict

class Derived_B(Base):
    def __init__(self, regarg1, regarg2, optarg2):
        super().__init__(regarg1, regarg2, optarg2)

    def create_dict(self):
        # some implementation
        return dict      

EDIT: Just to make it clear, I don't quite know how to handle the abstractmethod in the base class properly as the senior dev would like to use it as follows:

def main():
    b = Base(100, 200)
    for i in get_subset_list(30):
        print(i)

But dict in the Base class is not defined because it is defined in the derived classes and therefore will output the following error:

NameError: name 'abstractmethod' is not defined

2

There are 2 answers

1
lucianopaz On BEST ANSWER

My suggestion is that you use a factory class method in the Base class. You would only have to be able to determine the Derived class that you would need to return depending on the supplied input. I'll copy an implementation that assumes that you wanted a Derived_A if you supply the keyword optarg1, and Derived_B if you supply the keyword optarg2. Of course, this is completely artificial and you should change it to suit your needs.

import abc
class Base(metaclass = abc.ABCMeta):
    @classmethod
    def factory(cls,reqarg1,reqarg2,**kwargs):
        if 'optarg1' in kwargs.keys():
            return Derived_A(reqarg1=reqarg1,reqarg2=reqarg2,optarg1=kwargs['optarg1'])
        elif 'optarg2' in kwargs.keys():
            return Derived_B(reqarg1=reqarg1,reqarg2=reqarg2,optarg2=kwargs['optarg2'])
        else:
            raise ValueError('Could not determine Derived class from input')
    def __init__(self, reqarg1, reqarg2, optarg1=0.123, optarg2=False):
        self.reqarg1 = reqarg1
        self.reqarg2 = reqarg2
        self.optarg1 = optarg1
        self.optarg2 = optarg2
        self.dict = self.create_dict()
    @abc.abstractmethod
    def create_dict(self):
        pass

    def get_subset_list(self, id):
        return [item for item in self.dict.values() if item.id == id] 

    def __iter__(self):
        for item in self.dict.values():
            yield item

class Derived_A(Base):
    def __init__(self, reqarg1, reqarg2, optarg1):
        super().__init__(reqarg1, reqarg2, optarg1=optarg1)

    def create_dict(self):
        # some implementation
        dict = {'instanceOf':'Derived_A'}
        return dict

class Derived_B(Base):
    def __init__(self, reqarg1, reqarg2, optarg2):
        super().__init__(reqarg1, reqarg2, optarg2=optarg2)

    def create_dict(self):
        # some implementation
        dict = {'instanceOf':'Derived_B'}
        return dict

This will allow you to always create a Derived_X class instance that will have the create_dict non-abstract method defined for when you __init__ it.

In [2]: b = Base.factory(100, 200)
ValueError: Could not determine Derived class from input

In [3]: b = Base.factory(100, 200, optarg1=1213.12)

In [4]: print(b.dict)
{'instanceOf': 'Derived_A'}

In [5]: b = Base.factory(100, 200, optarg2=True)

In [6]: print(b.dict)
{'instanceOf': 'Derived_B'}

Moreover, you can have more than one factory method. Look here for a short tutorial.

3
Uriel On

You don't have to use keyword arguments at all; just define the variables with their default value in the parameters section of the function, and send only the parameters you want to send from the derived classes.

Note that parameters with a default value doesn't have to be supplied - that way you can have a function with a ranging number of arguments (where the arguments are unique, and can not be treated as a list).

Here is a partial example (taken from your code):

import abc

class Base(metaclass = abc.ABCMeta):
    def __init__(self, reqarg1, reqarg2, optarg1 = 0.123, optarg2 = False):
        self.reqarg1, self.reqarg2 = reqarg1, reqarg2
        self.optarg1, self.optarg2 = optarg1, optarg2
    ...

class Derived_A(Base):
    def __init__(self, regarg1, regarg2, optarg1):
        super().__init__(regarg1, regarg2, optarg1=optarg1)
    ...

class Derived_B(Base):
    def __init__(self, regarg1, regarg2, optarg2):
        super().__init__(regarg1, regarg2, optarg2=optarg2)
    ... 

EDIT: As the question update, I would give just a small note - abstract method is there to make sure that a mixed list of some derived Base objects can call the same method. Base object itself can not call this method - it is abstract to the base class, and is just there so we can make sure every derived instance will have to implement this method.