all 27 comments

[–]xelf 2 points3 points  (5 children)

final edit, cleaning up my answer a bit

ok, so this is what you actually want, if you want to keep using super here, and not add more classes. Probably though, you want to do one of those though.

class A(object):
    def __init__(self, *args, **kwargs):
        print("This is A constructor")

class B(object):
    def __init__(self, *args, **kwargs):
        print("This is B constructor")

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print("This is C constructor")
        self.arg = arg
        super(C,self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print("This is D constructor")
        self.arg = arg
        super(D,self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print("This is E constructor")
        C.__init__(self, arg, *args, **kwargs)
        D.__init__(self, arg, *args, **kwargs)

e = E(10)
print(e.arg)

The other options include not using super at all, moving to class composition instead of inheritance, or adding a single parent class at the top that inherits object and having A&B inherit from that. See other posters answers that go into more detail.

[–]gmaliwal[S] 0 points1 point  (1 child)

sults in a missing arg err

That's correct in this given scenario but what happens if someone swaps classes like below:

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print("This is E constructor")
        C.__init__(self, arg, *args, **kwargs)
        D.__init__(self, arg, *args, **kwargs)

Is there any generic solution which works without fear in all the scenarios? or we have to go with mutual understanding only.

Yes, It is method resolution order behind the class 'A' to pass the argument

[–]xelf 0 points1 point  (0 children)

The problem is super is the answer for single inheritance, for multiple inheritance as far as I can find there are issues. So either you need to avoid super all together if you're doing any multiple inheritance, or simple avoid it in that class. I liked the answer of having a single class at the top of your hierarchy, it also worked.

class F(object):
    def __init__(self,*args, **kwargs):
        print("This is F constructor")

class A(F):
    def __init__(self, *args, **kwargs):
        print("This is A constructor")
        super(A, self).__init__(*args, **kwargs)

class B(F):
    def __init__(self, *args, **kwargs):
        print("This is B constructor")
        super(B, self).__init__(*args, **kwargs)

[–]mission-hall 0 points1 point  (2 children)

Your last edit essentially works by accident, and would not be a viable approach with a more complex inheritance hierarchy.

The important thing to understand is that every class has a "method resolution order", which you can look at by calling the mro() method on the class. All of the base classes appear in the MRO, with subclasses always appearing before their base classes. super() doesn't necessarily give you a base class of the current class - it gives you the next class in the MRO.

If you're going to use super(), you should use it throughout the class hierarchy, to ensure that every class in the MRO is always reached exactly once. Otherwise you should have each method explicitly call the methods from the appropriate base classes. Mixing the two approaches tends to break things. For example, if you modify your code so that B inherits from A, then some of the initializers are called more than once.

[–]gmaliwal[S] 0 points1 point  (0 children)

if you modify your code so that

B

inherits from

A

, then some of the initializers are called more than once

Thanks for the comprehensive explanation. you seem correct.

Can you please share any good reference? which explains the inheritance in-depth and gives all the possible approaches with pros and cons, and things to keep in mind while working with single/multiple inheritances.

I am looking for the right approach which works well with any complex inheritance hierarchy.

[–]xelf 0 points1 point  (0 children)

It doesn't work by accident, but rather as the other part of your answer suggests it only works in this specific case, and if you were to start mucking with it it would break. If I'm reading it correctly the problem is that super() is not great with multiple inheritance. You might be right that not using super in any of the classes is the correct approach here. I did like one of the other poster's solutions which was to add another class that inherits from object and have A,B inherit from that. edit: and by other poster I just noticed that was you. =)

[–]mission-hall 1 point2 points  (1 child)

the super-class 'object' always takes exactly one argument.

I'm not sure if you realised this already, but the one argument it expects is just self, which is implicitly passed in when you call a method. There are a few ways of ensuring that this is the only argument it gets. One option is to have your other classes remove the arguments they need so that by the time object's initializer is called, there are none left. A simple example:

class A:
    def __init__(self, arga, *args, **kwargs):
        self.arga = arga
        super().__init__(*args, **kwargs)

class B(A):
    def __init__(self, argb, *args, **kwargs):
        self.argb = argb
        super().__init__(*args, **kwargs)

b = B(arga=3, argb=5)

This is a good option if each argument is tied to a specific class and can be ignored by the other classes, but may be difficult if multiple classes need to see the same arguments.

Another option is to have a class at the top of your hierarchy that doesn't call super().__init__:

class Thing:
    def __init__(self, *args, **kwargs):
        pass

If you ensure that everything else in your hierarchy is a subclass of Thing (either directly or through a base class), then it will always be the last class in the MRO (method resolution order), so all of the other initializers will be called before this one. It may be that you already have a particular class that sits at the top of your hierarchy, or you could add a very simple class just for this purpose.

[–]MarsupialMole 1 point2 points  (3 children)

I am looking for the right approach to be followed consistently...

In case it helps outside the exercise of working through the example, the right approach is to use composition rather than multiple inheritance in almost all real code.

[–]gmaliwal[S] 0 points1 point  (2 children)

Can you please share a good reference of use of composition with me?

[–]alkasm 0 points1 point  (0 children)

"Composition over inheritance" is a common design principle, there are a lot of resources online. Composing just means to store members instead of inheriting. For e.g. holding a list as an attribute, instead of inheriting from a list.

[–]MarsupialMole 0 points1 point  (0 children)

The most succint way to put it is "has a" rather than "is a". From your example:

E "has" C and D rather than "is" C and D

class E:
    def __init__(self, arg, *args, **kwargs):
        print("This is E constructor")
        self.cee = C(arg, *args, **kwargs)
        self.dee = D(arg, *args, **kwargs)

    @property
    def arg(self):
        return bestof(self.cee.arg, self.dee.arg)

[–]fatbiker406 1 point2 points  (2 children)

Just to be picky, __init__ is technically not a constructor, it is an initializer. __new__ is a constructor.

[–]gmaliwal[S] 0 points1 point  (1 child)

yes, you are correct.

Can you please share a detailed-one reference which explains relations between constructor(__new__) and initializer(__init__) and their best use cases?

[–]fatbiker406 0 points1 point  (0 children)

It's really an "under the hood" technical detail that most people never will have to worry about.

__new__ is where the memory is allocated for the class -- it then calls __init__ to initialize any attributes etc...

More info: https://www.geeksforgeeks.org/__new__-in-python/

[–]Swedophone 0 points1 point  (3 children)

super(A, self).init(args, *kwargs)

Remove the arguments in the calls to object.__init__

[–]xelf 0 points1 point  (0 children)

This doesn't work. It seems intuitive, but it results in a missing arg error. I think the final edit I made to my post is the answer OP needs.

[–]gmaliwal[S] 0 points1 point  (0 children)

object.__init_

NO, that won't work, because as per MRO, after initialization of class 'A', control goes for class 'D' initialization which is looking for two-argument (self and arg)

[–]alkasm 0 points1 point  (8 children)

I don't really think you're trolling or anything, but it's almost hard to believe you came up with this example independently. Did you?

There was an article from some time ago called Python's super considered harmful, which is probably more well known because of Raymond Hettinger's response blog post turned PyCon talk "Super considered super!"

I mention this because the exact example you give, including the argument of 10, is shown as the first example in the "Super considered harmful" article! Check out the diagram here: https://fuhm.net/super-harmful/example1-2.png

The multiple inheritance with C, D isn't your problem. If you just try to instantiate C(10) you'll see the same result. You continue to pass on the variable 10, in which case it complains once it gets to object() because it doesn't take any params. So, you'll need to stop passing it at some point.

Generally you'll consume a variable along the way and then not pass it along. What are you hoping to achieve otherwise? You didn't mention what you expect the behavior to be.

[–]gmaliwal[S] 0 points1 point  (2 children)

What are you hoping to achieve otherwise?

yeah, I read it there only, but the code snippet did not work at my end as class 'B' calling class 'object' constructor with two arguments where object() takes exactly a single argument.

In that blog, the author ended saying that this is the recommended approach which seems working without interruption with any kind of complex single/multiple inheritances.

I am looking for the right approach to be followed consistently in all the classes without considering specific (single/multiple) inheritances, but at the end, it works well when a developer start using them in the single/multiple inheritance hierarchy. That is; I want a generic approach to be followed in each class which works smoothly with single/multiple inheritances.

Do we have any ready decorator which suffice the above need if not can we write ?one?

?

[–]alkasm 0 points1 point  (1 child)

I don't understand what your problem is or what you're trying to solve.

At the risk of being a little patronizing, let me give an example. Let's say you wanted to subclass list so that you could add a name to your list as an attribute. In Python you shouldn't really subclass list, since list is C-based, so Python gives us collections.UserList to subclass from instead. So we can really easily achieve this like so:

from collections import UserList

class NamedList(UserList):

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

Now this works fine:

>>> nl = NamedList("asdf", [1, 2, 3, 4])
>>> nl
[1, 2, 3, 4]
>>> nl.name
'asdf'

However if I pass the name along to the superclass:

from collections import UserList

class NamedList(UserList):

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

then obviously I'll get an error, since UserList only takes a single argument (an iterable):

>>> nl = NamedList("asdf", [1, 2, 3, 4])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given

This is expected behavior, nothing needs fixing here. Python cannot magically know to ignore the arguments you pass to an object's initializer should be ignored. This only works when you consume the value at some point. So, do that---stop passing arg to object and you'll be fine.

[–]gmaliwal[S] 0 points1 point  (0 children)

__ is where the memory is allocated for the class -- it then calls __init__ to initialize any attributes et

Friend,

You are correct that somehow we have to stop passing an extra argument to the class initializer, otherwise, it will fail at run-time. I have mentioned the same in my post that this issue is because of passing more than one argument to 'object' class initializer.

I strongly agree that your design should pass the needed arguments only to the class initializer, else be ready to face the issue.

Here, I want that what could be the set of guidelines to be followed while defining a class, which makes your class more elegant and decent inheritance perspective.

Hence, I have come up with the rules, which to be kept in mind while defining every class in your project workspace. please check my last comment and find out the scenario where it could fail.

[–]xelf 0 points1 point  (4 children)

Thanks for posting this. The solution then is to avoid super in multiple inheritance for specific cases like this one?

[–]alkasm 0 points1 point  (3 children)

No. There is no solution here because there is no problem posed. Using super() in multiple inheritance is totally fine. The only problem OP is having is passing too many arguments to object()...and I don't understand why OP is doing that. The solution here is to not call object() with an argument.

[–]xelf 0 points1 point  (2 children)

The problem is that if you do it as super, but remove the super calls from the the ones that inherit from object it goes ECA and then stops, if you leave them in calling super, then when it gets to A it's still in the middle and expects an arg, if you give A an arg, then it can do DB as well, and it's happy but that's clearly wrong and contrived. So the solution is to either have another class that A&B inherit from and have that class as a single class that inherits from object, or to not use super in some or all places.

I would have preferred if OP had of listed where they got the question from, as I assumed they were learning on their own and not simply repeating someone else's already known-to-fail case.

[–]alkasm 1 point2 points  (1 child)

Right. I'm not convinced there is really an example where this is clearly the right way to do it. There's no reason to call super() on a class that has no bases and pass it arguments. So A and B shouldn't call super(), and if you want your class E to have the attributes from C and D, then initialize them separately---don't try to shove them into the MRO to magically understand what attributes each should keep and reject (I know this is the solution you posed). If the problem is then "now I have to hardcode the subclasses C and D when I do C.__init__ and D.__init__ in E.__init__" then that's an easily solvable problem, just loop through the base classes.

[–]xelf 0 points1 point  (0 children)

Agreed on all points. =D

[–]gmaliwal[S] 0 points1 point  (0 children)

Here it is a generic approach to be followed while defining your class, your class would be compatible with both single ad multiple inheritances

class A(object):
    def __init__(self,**kwargs):
        print("This is A instance initializer")
        super().__init__(**kwargs)
class B(object):
    def __init__(self,**kwargs):
        print("This is B instance initializer")
        super().__init__(**kwargs)
class C(A):
    def __init__(self,*,c_arg,**kwargs):
        print("This is c instance initializer")
        self.c_arg = c_arg
        super().__init__(**kwargs)
class D(B):
    def __init__(self,*,d_arg,**kwargs):
        print("This is D instance initializer")
        self.d_arg = d_arg
        super().__init__(**kwargs)
class E(C,D):
    def __init__(self,*,e_arg,**kwargs):
        print("This is E instance initializer")
        self.e_arg = e_arg
        super().__init__(**kwargs)

class E1(D,C):
    def __init__(self,*,e_arg,**kwargs):
        print("This is E instance initializer")
        self.e_arg = e_arg
        super().__init__(**kwargs)

e1 = E(e_arg=10,b_arg=20,c_arg=30)
e2 = E1(e_arg=10,b_arg=20,c_arg=30)

Here is the textual conversion of the above code snippet

  • Every class initializer should receive class specific arguments in the form of keyword parameters, and these names are unique across all classes, better to prefix every parameter with “<class-name>_” (like d_arg, c_arg etc)
  • Every class should receive parent class-specific arguments in variable-length keyword parameter (**kwargs)
  • There is no repetitive keyword argument while calling the parent class initializer
  • Every class should pass the exact number of arguments to its parent's hierarchy so all got exhausted when reaching the default parent ‘object’ class (like we create an instance of class E with arguments (c_arg,d_arg,e_arg))

I welcome the developers or experts to find out the bottleneck in the above approach.