Home > OS >  How to: safely call super constructors with different arguments
How to: safely call super constructors with different arguments

Time:12-14

I have seen super().__init__(*args) used to call the super constructor safely (in a way that does not fail to diamond inheritence). However I cannot find a way to call different super constructors with different arguments in this way.

Here is an example illustraiting the problem.

from typing import TypeVar, Generic

X = TypeVar("X")
Y = TypeVar("Y")

class Base:
  def __init__(self):
    pass

class Left(Base, Generic[X]):
  def __init__(self, x:X):
    super().__init__()
    self.lft = x

class TopRight(Base, Generic[Y]):
  def __init__(self, y:Y):
    super().__init__()
    self.rgh = y

class BottomRight(TopRight[Y], Generic[Y]):
  def __init__(self, y:Y):
    super().__init__(y   y)

class Root(Left[X], BottomRight[Y], Generic[X, Y]):
  def __init__(self, x:X, y:Y):
    pass #issue here

    #does not work
    #super().__init__(x)
    #super().__init__(y)

    #calls base twice
    #Left[X].__init__(x)
    #BottomRight[Y].__init__(y)

How do I call Left.__init__(x) and BottomRight.__init__(y) seperately and safely?

CodePudding user response:

The thing is that to be use in cooperative form, the intermediate classes have to accept the arguments that are not "aimed" at them, and pass those on on their own super call, in a way that becomes transparent.

You them do not place multiple calls to your ancestor classes: you let the language runtime do that for you.

Your code should be written:

from typing import Generic, TypeVar

X = TypeVar("X")
Y = TypeVar("Y")

class Base:
  def __init__(self):
      pass

class Left(Base, Generic[X]):
  def __init__(self, x:X, **kwargs):
    super().__init__(**kwargs)   
    self.lft = x

class TopRight(Base, Generic[Y]):
  def __init__(self, y:Y, **kwargs):
    super().__init__(**kwargs)
    self.rgh = y

class BottomRight(TopRight[Y], Generic[Y]):
  def __init__(self, y:Y, **kwargs):   # <- when this is executed, "y" is extracted from kwargs
    super().__init__(y=y   y, **kwargs)  # <-  "x" remains in kwargs, but this class does not have to care about it.

class Root(Left[X], BottomRight[Y], Generic[X, Y]):
  def __init__(self, x:X, y:Y):
      super().__init__(x=x, y=y)  # <- will traverse all superclasses,  "Generic" being last

Also, note that depending on your project's ends, and final complexity, these type annotations may gain you nothing, and instead, add complexity to a code otherwise trivial. They are not always a gain in Python projects, although due to circunstances the tooling (i.e. IDEs), might recommend them.

Also, check this similar answer from a few days ago, were I detail a bit more of Python method resolution order mechanisms, and point to the official documentation on them: In multiple inheritance in Python, init of parent class A and B is done at the same time?

  • Related