1
class Base:

    def __init__(self, x, y, *args, **kwargs):
        pass


class A(Base):

    def __init__(self, x, y, z, *args, **kwargs):
        super().__init__(x, y, *args, **kwargs)


class B(Base):

    def __init__(self, x, y, t, *args, **kwargs):
        super().__init__(x, y, *args, **kwargs)


class C(A, B):

    def __init__(self, x, y, z, t, *args, **kwargs):
        super().__init__(x, y, z, *args, **kwargs)


C(1, 2, 3, 4)
Traceback (most recent call last):
    File "test.py", line 25, in <module>
        C(1, 2, 3, 4)
    File "test.py", line 22, in __init__
        A.__init__(self, x, y, z, *args, **kwargs)
    File "test.py", line 10, in __init__
        super().__init__(x, y, *args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 't'

I first try to add '*args' and '**kwargs', but it didn't work. I wonder why this happen? How to fix? Could someone explain this for me? Thank you!

Kevin Hsu
  • 23
  • 4

1 Answers1

3

You are discarding t altogether; *args is empty when you call C(1, 2, 3, 4); the value of the 4th parameter isn't automatically added to it, so B.__init__ will never see it.

Likewise, A.__init__ will cause B.__init__ to only be called with 2 arguments, which is where the error comes from: no 3rd argument for B.__init__.

The ordering of positional parameters can be an issue as well; the standard advice is to use keyword arguments exclusively, and let each __init__ extract the keyword arguments needed for its own parameters. For example,

class Base:

    def __init__(self, *, x, y, **kwargs):
        super().__init__(**kwargs)
        self.x = x
        self.y = y


class A(Base):

    def __init__(self, *, z, **kwargs):
        super().__init__(**kwargs)
        self.z = z


class B(Base):

    def __init__(self, *, t, **kwargs):
        super().__init__(**kwargs)
        self.t = t


class C(A, B):
    pass


C(x=1, y=2, z=3, t=4)

Now there is no ambiguity about which positional arguments are consumed by which __init__ method: A.__init__ gets z and leaves x ,y, and t for classes higher up the MRO to deal with. B.__init__ extracts t, leaving x and y. Base extracts x and y, leaving nothing (as is correct) to be passed on to object.__init__.

(Base.__init__ uses super as well, because you don't know what other class Foo might use Base in a way that Base will not be the last class in Foo's MRO. Only object.__init__ does not use super(); it's sole purpose is to terminate the chain of super calls, as there's nothing in a bare object instance to initialize.)

chepner
  • 497,756
  • 71
  • 530
  • 681