Search code examples
pythonconstructorinitializationsuperclassdiamond-problem

Superclasses' constructors with different lists of arguments calls in Python


I wrote classes with diamond inheritance. Were there are two classes at same level the constructors have different length of arguments list and depending on declaration order in list of bases declaration everything works fine or error in thrown immediately

class DFT(Common.BaseAS, Common.Signal):

    def __init__(self, fs, N, history_len=1, strict=False):

        super().__init__(fs, N, history_len, strict, np.complex)


class BaseAS(abc.ABC, AnalysisResultSaver):
   No constructor here

class AnalysisResultSaver(Base):

    def __init__(self, fs=8000, N=250, history_len=1, strict=False, dtype=None):

        super().__init__(fs, N, dtype)

class Signal(Base):

    def __init__(self, fs, N, dtype=None):
        super().__init__(fs, N, dtype)

class Base:

    def __init__(self, fs, N, dtype=None):
       Stuff  

Constructors are called in order: DFT; AnalysisResultSaver; Signal; Base;

In this case everything works fine but my question is 1) how are arguments passed to Signal constructor if there's no direct indication which arguments are right ones, is it just trimmed to first two?

But if I change order of bases in DFT then I get

super().__init__(fs, N, history_len, strict, np.complex)
TypeError: __init__() takes from 3 to 4 positional arguments but 6 were given

I know it changes mro but in first case it works fine

and if I want to call constructors directly by Common.BaseAS.__init__() and Common.Signal.__init__() than Signal constructor is called twice so somehow call to BaseAS calls Signal constructor even though it's not its parent.

Common.BaseAS.__init__(self, fs, N, history_len, strict, np.complex)
Common.Signal.__init__(self, fs, N)

So 2) how can BaseAS call Signal constuctor?


Solution

  • The answer by @KSab is correct, but I will add this as it helps illustrate what is happening (and was suggested in that answer). I modified your code a bit to show exactly what is happening and in what order as these objects are constructed. Here is the code:

    import abc
    import numpy as np
    
    class Base:
        def __init__(self, fs, N, dtype=None):
            print('='*80)
            print(f"Base fs: {fs}")
            print(f"Base N: {N}")
            print(f"Base dtype: {dtype}")
    
    
    class Signal(Base):
        def __init__(self, fs, N, dtype=None):
            print('='*80)
            print(f"Signal self: {self}")
            print(f"Signal fs: {fs}")
            print(f"Signal N: {N}")
            print(f"Signal dtype: {dtype}")
            print("Signal(Base) will now call:  super().__init__(fs, N, dtype)")
            super().__init__(fs, N, dtype)
    
    
    class AnalysisResultSaver(Base):
        def __init__(self, fs=8000, N=250, history_len=1, strict=False, dtype=None):
            print('='*80)
            print(f"ARS self: {self}")
            print(f"ARS fs:{fs} ")
            print(f"ARS N: {N}")
            print(f"ARS history_len: {history_len}")
            print(f"ARS strict: {strict}")
            print(f"ARS dtype: {dtype}")
            print("ARS(Base) will now call:  super().__init__(fs, N, dtype)")
            super().__init__(fs, N, dtype)
    
    class BaseAS(abc.ABC, AnalysisResultSaver):
        pass
    
    class DFT(BaseAS, Signal):
        def __init__(self, fs, N, history_len=1, strict=False):
            print('='*80)
            print(f"DFT self: {self}")
            print(f"DFT fs:{fs} ")
            print(f"DFT N: {N}")
            print(f"DFT history_len: {history_len}")
            print(f"DFT strict: {strict}")
            print("DFT(BaseAS, Signal) will now call: super().__init__(fs, N, history_len, strict, np.complex)")
            super().__init__(fs, N, history_len, strict, np.complex)
    
    
    
    my_d = DFT('fs', 32, 10, True)
    

    It will produce this output:

    ================================================================================
    DFT self: <__main__.DFT object at 0x10cabe310>
    DFT fs:fs
    DFT N: 32
    DFT history_len: 10
    DFT strict: True
    DFT(BaseAS, Signal) will now call: super().__init__(fs, N, history_len, strict, np.complex)
    ================================================================================
    ARS self: <__main__.DFT object at 0x10cabe310>
    ARS fs:fs
    ARS N: 32
    ARS history_len: 10
    ARS strict: True
    ARS dtype: <class 'complex'>
    ARS(Base) will now call:  super().__init__(fs, N, dtype)
    ================================================================================
    Signal self: <__main__.DFT object at 0x10cabe310>
    Signal fs: fs
    Signal N: 32
    Signal dtype: <class 'complex'>
    Signal(Base) will now call:  super().__init__(fs, N, dtype)
    ================================================================================
    Base fs: fs
    Base N: 32
    Base dtype: <class 'complex'>
    ================================================================================
    

    Also, this is the MRO for each class:

    >>> DFT.mro()
    [<class '__main__.DFT'>, <class '__main__.BaseAS'>, <class 'abc.ABC'>, <class '__main__.AnalysisResultSaver'>, <class '__main__.Signal'>, <class '__main__.Base'>, <class 'object'>]
    >>> BaseAS.mro()
    [<class '__main__.BaseAS'>, <class 'abc.ABC'>, <class '__main__.AnalysisResultSaver'>, <class '__main__.Base'>, <class 'object'>]
    >>> AnalysisResultSaver.mro()
    [<class '__main__.AnalysisResultSaver'>, <class '__main__.Base'>, <class 'object'>]
    >>> Signal.mro()
    [<class '__main__.Signal'>, <class '__main__.Base'>, <class 'object'>]
    >>> Base.mro()
    [<class '__main__.Base'>, <class 'object'>]