Should classes that inherit directly from object
call super().__init__()
?
You seem to search a simple "yes or no" answer for this question, but unfortunately the answer is "it depends". Furthermore, when deciding if you should call super().__init__()
, it is somewhat irrelevant whether or not a class inherits directly from object
. What is invariant is that, if object.__init__
is called, it should be called without arguments - since object.__init__
does not accept arguments.
Practically, in cooperative inheritance situations, this means you must ensure that all arguments are consumed before object.__init__
gets invoked. It does not mean you should try to avoid object.__init__
being invoked. Here is an example of consuming args before invoking super
, the response
and request
context has been popped out of the mutable mapping kwargs
.
I mentioned earlier that whether or not a class inherits directly from object
is a red herring1. But I didn't mention yet what should motivate this design decision: You should call super init [read: super anymethod
] if you want the MRO to continue to be searched for other initializers [read: other anymethod
s]. You should not invoke super if you want to indicate the MRO search should be stopped here.
Why does object.__init__
exist at all, if it doesn't do anything? Because it does do something: ensures it was called without arguments. The presence of arguments likely indicates a bug2. object
also serves the purpose of stopping the chain of super calls - somebody has to not call super, otherwise we recurse infinitely. You can stop it explicitly yourself, earlier, by not invoking super. If you don't, object
will serve as the final link and stop the chain for you.
Class MRO is determined at compile time, which is generally when a class is defined / when the module is imported. However, note that the use of super
involves many chances for runtime branching. You have to consider:
- Which arguments a method of the
super
is called with (i.e. which arguments you want to forward along the MRO)
- Whether or not super is called at all (sometimes you want to intentionally break the chain)
- Which arguments, if any, the
super
itself is created with (there is an advanced use case described below)
- Whether to call a proxied method before or after the code in your own method (put super first if need to access some state the proxied methods set up, put super last if you're setting up some state that the proxied methods rely on being there already - in some cases you even want to put super in the middle somewhere!)
- The question asks mostly about
__init__
, but don't forget that super can be used with any other method, too
In rare circumstances, you might conditionally invoke a super
call. You might check whether your super()
instance has this or that attribute, and base some logic around the result. Or, you might invoke super(OtherClass, self)
to explicitly "step over" a link and manually traverse the MRO for this section. Yes, if the default behaviour is not what you wanted, you can hijack the MRO! What all these diabolical ideas have in common is an understanding of the C3 linearization algorithm, how Python makes an MRO, and how super itself uses the MRO. Python's implementation was more or less lifted from another programming language, where super was named next-method
. Honestly super
is a super-bad name in Python because it causes a common misconception amongst beginners that you're always invoking "up" to one of the parent classes, I wish they had chosen a better name.
When defining an inheritance hierarchy, the interpreter can not know whether you wanted to reuse some other classes existing functionality or to replace it with an alternate implementation, or something else. Either decision could be a valid and practical design. If there was a hard and fast rule about when and how super
should be invoked, it would not be left to the programmer to choose - the language would take the decision out of your hands and just do the right thing automatically. I hope that sufficiently explains that invoking super in __init__
is not a simple yes/no question.
If yes, how would you correctly initialize SuperFoo
?
(Source for Foo
, SuperFoo
etc in this revision of the question)
For the purposes of answering this part, I will assume the __init__
methods shown in the MCVE actually need to do some initialization (perhaps you could add placeholder comments in the question's MCVE code to that effect). Don't define an __init__
at all if the only you do is call super with same arguments, there's no point. Don't define an __init__
that's just pass
, unless you intentionally mean to halt the MRO traversal there (in which case a comment is certainly warranted!).
Firstly, before we discuss the SuperFoo
, let me say that NoSuperFoo
looks like an incomplete or bad design. How do you pass the foo
argument to Foo
's init? The foo
init value of 3
was hardcoded. It might be OK to hardcode (or otherwise automatically determine) foo's init value, but then you should probably be doing composition not inheritance.
As for SuperFoo
, it inherits SuperCls
and Foo
. SuperCls
looks intended for inheritance, Foo
does not. That means you may have some work to do, as pointed out in super harmful. One way forward, as discussed in Raymond's blog, is writing adapters.
class FooAdapter:
def __init__(self, **kwargs):
foo_arg = kwargs.pop('foo')
# can also use kwargs['foo'] if you want to leave the responsibility to remove 'foo' to someone else
# can also use kwargs.pop('foo', 'foo-default') if you want to make this an optional argument
# can also use kwargs.get('foo', 'foo-default') if you want both of the above
self._the_foo_instance = Foo(foo_arg)
super().__init__(**kwargs)
# add any methods, wrappers, or attribute access you need
@property
def foo():
# or however you choose to expose Foo functionality via the adapter
return self._the_foo_instance.foo
Note that FooAdapter
has a Foo
, not FooAdapter
is a Foo
. This is not the only possible design choice. However, if you are inheriting like class FooParent(Foo)
, then you're implying a FooParent
is a Foo
, and can be used in any place where a Foo
would otherwise be - it's often easier to avoid violations of LSP by using composition. SuperCls
should also cooperate by allowing **kwargs
:
class SuperCls:
def __init__(self, **kwargs):
# some other init code here
super().__init__(**kwargs)
Maybe SuperCls
is also out of your control and you have to adapt it too, so be it. The point is, this is a way to re-use code, by adjusting the interfaces so that the signatures are matching. Assuming everyone is cooperating well and consuming what they need, eventually super().__init__(**kwargs)
will proxy to object.__init__(**{})
.
Since 99% of classes I've seen don't use **kwargs
in their constructor, does that mean 99% of python classes are implemented incorrectly?
No, because YAGNI. Do 99% of classes need to immediately support 100% general dependency-injection with all the bells and whistles, before they are useful? Are they broken if they don't? As an example, consider the OrderedCounter
recipe given in the collections docs. Counter.__init__
accepts *args
and **kwargs
, but doesn't proxy them in the super init call. If you wanted to use one of those arguments, well tough luck, you've got to override __init__
and intercept them. OrderedDict
isn't defined cooperatively at all, really, some parent calls are hardcoded to dict
- and the __init__
of anything next in line isn't invoked, so any MRO traversal would be stopped in its tracks there. If you accidentally defined it as OrderedCounter(OrderedDict, Counter)
instead of OrderedCounter(Counter, OrderedDict)
the metaclass bases would still be able to create a consistent MRO, but the class just wouldn't work at all as an ordered counter.
In spite of all these shortcomings, the OrderedCounter
recipe works as advertised, because the MRO is traversed as designed for the intended use-case. So, you don't even need to do cooperative inheritance 100% correctly in order to implement a dependency-injection. The moral of the story is that perfection is the enemy of progress (or, practicality beats purity). If you want to cram MyWhateverClass
into any crazy inheritance tree you can dream up, go ahead, but it is up to you to write the necessary scaffolding to allow that. As usual, Python will not prevent you to implement it in whatever hacky way is good enough to work.
1You're always inheriting from object, whether you wrote it in the class declaration or not. Many open source codebases will inherit from object explicitly anyway in order to be cross-compatible with 2.7 runtimes.
2This point is explained in greater detail, along with the subtle relationship between __new__
and __init__
, in CPython sources here.