-1

I wanted an inheritance chain to have an optional parameter. Most classes along the chain need to have the parameter become a member sometimes, but I also want to use the chain other times without the parameter becoming a member.

I thought of making the parameter optional a class and using import, but I want to avoid using class syntax for the member optional. And also because all classes along the chain are used in a dictionary as keys.

Alternatives to this? Am I doing something wrong? Is there a more Pythonic way?

class Top:

    def __init__(self, optional=None):

        if optional is not None:
            self.optional = optional
        return


class Middle(Top):

    def __init__(self, one, optional=None):

        if optional is not None:
            super().__init__(optional)

            self.one = one


class Bottom(Middle):

    def __init__(self, one, two, optional=None):

        if optional is not None:
            super().__init__(one, optional)
        else:
            super().__init__(one)

            self.two = two


a = Middle('one')
b = Middle('one', 'two')
c = Bottom('one', 'two')
d = Bottom('one', 'two', 'three')
Jundullah
  • 113
  • 2
  • 11
bad_coder
  • 11,289
  • 20
  • 44
  • 72

2 Answers2

1

Top already knows how to deal with optional=None; just pass the argument as-is. However, keep in mind that each class has to be prepared to receive and pass on unknown arguments, in case a class you didn't define inherits from any of these and adds its own arguments to __init__. (Consider a class X that inherits from both Top and some other class Foo, and Foo expects a keyword argument bar: X(1, 2, 3, bar=5) will eventually call Top.__init__ without bar having been removed yet. Keyword arguments are a Very Good Idea when using __init__ with super.)

class Top:

    def __init__(self, optional=None, **kwargs):
        super().__init__(**kwargs)
        if optional is not None:
            self.optional = optional


class Middle(Top):

    def __init__(self, one, optional=None, **kwargs):
        super().__init__(optional, **kwargs)
        self.one = one


class Bottom(Middle):

    def __init__(self, one, two, optional=None, **kwargs):
        super().__init__(one, optional, **kwargs)    
        self.two = two
chepner
  • 497,756
  • 71
  • 530
  • 681
1

All you need is to have the if statement in the Top (parent/base) class:

class Top:
    def __init__(self, optional=None):
        if optional is not None:
            self.optional = optional

# (you don’t need an explicit return in __init__)

class Middle(Top): 
    def __init__(self, one, optional=None):
        super().__init__(optional)
        self.one = one

# this applies to Bottom class as well

Now it doesn’t matter whether you provide this optional or not. Say you call Middle with no optional, the one attribute is set as expected and Top.__init__ is called with None because the default parameter in Middle sets it to this if not provided. Since your parameter default is None in all classes, the presence (or lack of presence) of the optional is carried through, essentially.

Personally, I wouldn’t even include the if-statement. I would set the attribute as None so that later in your code if you try to access obj.optional, no AttributeError is raised. I believe one should keep attributes consistent through subclasses if they are inherited so that anyone trying to use subclasses expect that all parameters used in __init__ are used or set as attributes through the numerous super() calls up the inheritance chain and are not lost.

N Chauhan
  • 3,407
  • 2
  • 7
  • 21