0

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

user123
  • 45
  • 6
  • https://en.wikipedia.org/wiki/Liskov_substitution_principle – Ignacio Vazquez-Abrams Dec 20 '16 at 16:56
  • 1
    Python is not a strongly typed language. ie. You don't have to worry about this sort of things. – Abdelhakim AKODADI Dec 20 '16 at 17:40
  • @IgnacioVazquez-Abrams: thanks for your suggestion; I will look into it – user123 Dec 20 '16 at 17:53
  • @AbdelhakimAkodadi: Isn't Python a strongly typed language? Can you expand more your way of thinking? – user123 Dec 20 '16 at 17:56
  • @AbdelhakimAkodadi is wrong, python is strongly typed. He probably means dynamic, see a good explanation [here](http://stackoverflow.com/questions/11328920/is-python-strongly-typed#11328980). The only reason to derive in Python is if it's logical to do so, as in the methods you implement in the base should work for the sons. – kabanus Dec 20 '16 at 18:20
  • Maybe you could implement a [factory](http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html) method in the `Base` class to return the appropriate `Derived`. That way you don't have to worry about the abstract methods because you will always get an instance of `Derived` instead of `Base`. – lucianopaz Dec 20 '16 at 18:24
  • Go through this link (http://stackoverflow.com/questions/4814523/abstractmethod-is-not-defined) .Hope it will help you – kiran_raj_r Dec 20 '16 at 18:32
  • 2
    I think that's actually more of a question for [SoftwareEngineering](http://softwareengineering.stackexchange.com/) because it's about software architecture (I may be wrong). – MSeifert Dec 20 '16 at 18:32
  • @kabanus: Thanks for the explanation. – user123 Dec 20 '16 at 20:52

2 Answers2

1

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.

Uriel
  • 15,579
  • 6
  • 25
  • 46
  • Thanks for your suggestion. My biggest concern is about the abstract method. I don't know how to handle that cleanly. I will reword my question to make it more clear. – user123 Dec 20 '16 at 18:12
  • Well, for that I can tell in one word - yes, the way you do it is good. However - you may have wanted to use `return self.dict` (`dict` is just an undeclared local var). – Uriel Dec 20 '16 at 20:36
  • I am in the same train of thought with you. – user123 Dec 20 '16 at 20:49
1

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.

lucianopaz
  • 1,212
  • 10
  • 17