0

Given the following classes A, B, and C:

class A:
    def __init__(self, a, aa, aaa):
        self.a = a
        self.aa = aa
        self.aaa = aaa


class B:
    def __init__(self, b, bb, bbb):
        self.b = b
        self.bb = bb
        self.bbb = bbb


class C(A, B):
    def __init__(self, **kwargs):
        super(C, self).__init__(**kwargs)

I want to avoid having to repeat all the superclasses parameters a, aa, aaa, b, bb, bbb, in C definition:

class C(A, B):
    def __init__(self, a, aa, aaa, b, bb, bbb):
        super(C, self).__init__(**kwargs)

and somehow pass A and B kwargs to be resolved in super().__init__ call but this is not possible using the way I described and will result in an error:

>>> c = C(a=1, aa=2, aaa=3, b=4, bb=5, bbb=6)
TypeError: A.__init__() got an unexpected keyword argument 'b'

The correct way of doing so is calling A.__init__(self, **a_kwargs) and B.__init__(self, **b_kwargs) but as I said this creates redundant parameters I'm trying to avoid. Is there a better way to achieve the same thing?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • What do you consider a "redundant parameter"? Are you concerned about having to write ``**kwargs`` everywhere, or about being able to pass an undeclared parameter to ``A`` and ``B``? – MisterMiyagi Mar 14 '22 at 14:20
  • I'll edit to explain –  Mar 14 '22 at 14:21
  • @MisterMiyagi for some reason, the stupid edit doesn't work so, what I mean by redundant is having to do: `class C: def __init__(a, aa, aaa, b, bb, bbb):` –  Mar 14 '22 at 14:26
  • 1
    @sK500: Try the edit again. – quamrana Mar 14 '22 at 14:31
  • FWIW, the comments on the answers indicate you have quite more requirements than listed in the question. For example, ["each of the superclasses having unique parameter names is not always guaranteed"](https://stackoverflow.com/questions/71469007/how-to-minimize-redundancy-in-passing-kwargs-to-multiple-super-classes#comment126322199_71469175). Don't expect people to magically know these things or to scrap their answers when goalposts are moved; put *all* requirements in the question. – MisterMiyagi Mar 14 '22 at 14:42
  • 1
    Required reading: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – chepner Mar 14 '22 at 14:43
  • "what I mean by redundant is having to do ..." That's what ``**kwargs`` avoids but the question seems to reject it on grounds of the same reason. How would ``def __init__(self, **kwargs): ...`` in C be redundant? – MisterMiyagi Mar 14 '22 at 14:45
  • 2
    Please don't make more work for other people by vandalizing your posts. By posting on the Stack Exchange network, you've granted a non-revocable right, under the [CC BY-SA 4.0 license](//creativecommons.org/licenses/by-sa/4.0/), for Stack Exchange to distribute that content (i.e. regardless of your future choices). By Stack Exchange policy, the non-vandalized version of the post is the one which is distributed. Thus, any vandalism will be reverted. If you want to know more about deleting a post please see: [How does deleting work?](//meta.stackexchange.com/q/5221) – Adrian Mole Jul 03 '22 at 07:17
  • This post will now have been automatically flagged for moderator attention. I would strongly suggest that you desist from further vandalism, in the meantime. – Adrian Mole Jul 03 '22 at 07:20

2 Answers2

0

You can do this:

class A:
    def __init__(self, a, aa, aaa, **kwargs):
        self.a = a
        self.aa = aa
        self.aaa = aaa
        super().__init__(**kwargs)

class B:
    def __init__(self, b, bb, bbb, **kwargs):
        self.b = b
        self.bb = bb
        self.bbb = bbb
        super().__init__(**kwargs)

class C(A, B):
    pass

c = C(a=1, aa=2, aaa=3, b=4, bb=5, bbb=6)

Since the C's MRO is [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>], A will consume it's parameters and pass the rest to B.

  • If you need to pass positional arguments too, You can change it to:
class A:
    def __init__(self, a, aa, aaa, *args, **kwargs):
        self.a = a
        self.aa = aa
        self.aaa = aaa
        super().__init__(*args, **kwargs)

class B:
    def __init__(self, b, bb, bbb, *args, **kwargs):
        self.b = b
        self.bb = bb
        self.bbb = bbb
        super().__init__(*args, **kwargs)

class C(A, B):
    pass
S.B
  • 13,077
  • 10
  • 22
  • 49
  • What happens if both `A` and `B` have a common parameter `x`? –  Mar 14 '22 at 14:28
  • @sK500 In this setup, `A` takes it then an exception will raise because `B`'s `__init__` requires that parameter too. – S.B Mar 14 '22 at 14:32
  • For the very same reason, this solution may not be the most convenient because each of the superclasses having unique parameter names is not always guaranteed, but it definitely is for the given example. I don't understand why it requires a `super().__init__` call in `A` though, can you elaborate? –  Mar 14 '22 at 14:37
  • 3
    `B` should *also* be using `super` in exactly the same way `A` is. Then `C.__init__` does not even need to be defined. – chepner Mar 14 '22 at 14:41
  • @sK500 I got you. For example if `b` is the same in both, you can get the items like `kwargs['b']` and do not define any other keyword parameters, then send the `kwargs` to the super. – S.B Mar 14 '22 at 14:42
  • 2
    @sK500 Then `a` and `b` aren't (immediately) compatible for cooperative inheritance. A wrapper for one or the other which renames the duplicated parameter is necessary. – chepner Mar 14 '22 at 14:42
  • This solution makes A have unexpected side effects in every place that uses it, especially if you also subclass it in some other place, and breaks as soon as you do `C(B, A)` instead of `C(A, B)` – matszwecja Mar 14 '22 at 14:50
  • @matszwecja actually, this solution is "the obviouys way to do it", and it won't break in anyway if one does `C(B, A)` or `C(A, B)`: each class of A and B will remove their parameters from the passed keyword arguments and pass the remaining on. Nor are there any "unexpected side-effects". If all these KW parameters are needed, they all have to be passed to instantiate a class that descend from both, and all are consumed in the inheritance chain call using `super()` – jsbueno Apr 27 '22 at 13:42
  • (actually, what I wrote is correct for the first part: expecting keyword arguments for everything. One should not use positional-only parameters in a situation like this, and if one does, of course the order of each `__init__` method in the call chain matters) – jsbueno Apr 27 '22 at 13:44
  • @jsbueno My comment about code breaking was posted before the change - if you look at the latest edit, it added the code necessary to make it work in both cases. Always good to check edits on a month old thread if you're gonna necropost. – matszwecja Apr 27 '22 at 17:18
-2

You could use built-in signature(callable) function to obtain expected keyword arguments for each __init__ and pass only those that the function expects like so. Whether this is good design, that is a matter of another discussion.

from inspect import signature

class A:
    def __init__(self, a, aa, aaa):
        self.a = a
        self.aa = aa
        self.aaa = aaa
        print(f"A obj: {a=}, {aa=}, {aaa=}")

class B:
    def __init__(self, b, bb, bbb):
        self.b = b
        self.bb = bb
        self.bbb = bbb
        print(f"B obj: {b=}, {bb=}, {bbb=}")

class C(A, B):
    def __init__(self, **kwargs):
        A_params = {k:v for k, v in kwargs.items() if k in signature(A.__init__).parameters.keys()}
        A.__init__(self, **A_params)
        B_params = {k:v for k, v in kwargs.items() if k in signature(B.__init__).parameters.keys()}
        B.__init__(self, **B_params)

c = C(a=1, aa=2, aaa=3, b=4, bb=5, bbb=6)

Output:

A obj: a=1, aa=2, aaa=3
B obj: b=4, bb=5, bbb=6
matszwecja
  • 6,357
  • 2
  • 10
  • 17
  • While it works, it requires more work than just redefining `a`, `aa`, `aaa`, `b`, `bb`, `bbb` in `C`'s definition –  Mar 14 '22 at 14:24
  • But it will work for any signature of parent classes `__init__` and does not require any change in the parent classes. – matszwecja Mar 14 '22 at 14:28
  • My point is minimizing redundancy/extra unnecessary lines and not applying whatever works. There are many ways to achieve the same thing, I'm looking for the shortest and simplest way. –  Mar 14 '22 at 14:30
  • Shortest is not always the best way. Actually, it rarely is. – matszwecja Mar 14 '22 at 14:35
  • the major distinction in this approach it is not using the language resource for multiple inheritance, that is `super`, and requires manually calling the parent classes. While there are scenarios where this can be the best/only possible approach, using `super()` calls in all involved classes is what one needs to do the majority of times. – jsbueno Apr 27 '22 at 13:47
  • @jsbueno Everything depends on the context: as you can read in [this answer](https://stackoverflow.com/a/50465583/9296093), if the classes are unrelated and standalone, my solution is the one proposed. That was the case in the question, as far as we know. – matszwecja Apr 27 '22 at 17:23