Search code examples
pythonpython-3.xoopderived-classbase-class

Using base class for all object creation


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


Solution

  • 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.