The shortest accurate description of super() comes from Raymond Hettinger's "Super Considered Super!" blog post and his PyCon 2015 talk: super() doesn't call your ancestors — it calls your children's ancestors. That line sounds confusing until you understand the Method Resolution Order (MRO), which is the linear list Python computes for each class to decide which method or attribute wins. Once you see the MRO, single inheritance becomes obvious, multiple inheritance becomes manageable, and the diamond problem that's haunted C++ for decades dissolves into "Python already solved this with C3 linearization." This guide walks through what super() actually returns, how the MRO is computed, how to read it for any class, and the cooperative inheritance pattern Hettinger's talk popularized. It's one of 10 explainers in our Python OOP complete guide.
Key Takeaways
super() returns a proxy object that delegates to the NEXT class in the MRO, not necessarily the lexical parent.
MRO is computed once per class using the C3 linearization algorithm. Inspect with ClassName.__mro__.
Hettinger's insight: super() calls your children's ancestors, not yours — the dispatch depends on the bottom class of the tree, not your class.
Python's MRO solves the diamond problem by ensuring each ancestor's __init__ runs exactly once when every class cooperates with super().
Use no-arg super() in Python 3 — super(ClassName, self) was Python 2 syntax and is now error-prone.
The diamond on the left, the linearized list on the right. C3 produces one list per class; super() walks it.
What super() Actually Returns
The first surprise: super() doesn't return a class. It returns a proxy object bound to the current class and instance, configured to delegate method lookups to whatever class comes NEXT in the MRO. The PSF docs on super() describe it directly: super "returns a proxy object that delegates method calls to a parent or sibling class of type."
The "or sibling" is the part that surprises beginners. In single inheritance, "next in the MRO" is just the parent class. In multiple inheritance, "next in the MRO" might be a SIBLING in the inheritance graph — a class that doesn't appear in your direct parent list at all.
class Animal:
def __init__(self, name):
self.name = name
print(f"Animal.__init__ ran for {name}")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Calls Animal.__init__
self.breed = breed
print(f"Dog.__init__ ran for {name} ({breed})")
rex = Dog("Rex", "Labrador")
# Animal.__init__ ran for Rex
# Dog.__init__ ran for Rex (Labrador)
This is the most common pattern: subclass __init__ calls super().__init__() first to let the parent set up its attributes, then adds its own. For single inheritance, that's the end of the story.
Why You Call super() in the First Place
The short answer: to make sure the parent's setup runs. __init__ usually does important work: assigning attributes, opening connections, registering things, setting defaults. If you skip the parent's __init__ by not calling super().__init__(), those things don't happen, and your subclass is incomplete.
# Wrong: skipping super().__init__ means Animal.__init__ never runs
class BrokenDog(Animal):
def __init__(self, name, breed):
self.breed = breed # No super().__init__()!
# self.name was never set!
rex = BrokenDog("Rex", "Labrador")
print(rex.name) # AttributeError: 'BrokenDog' object has no attribute 'name'
For longer-running classes (a database connection, an event listener) the missing initialization can fail in subtler ways: state looks set up but isn't, then breaks half an hour later when the unset attribute gets accessed.
Method Resolution Order (MRO)
The MRO is the ordered list Python uses to find methods and attributes for a class. Each class has its own MRO computed once at class creation time and cached as __mro__.
class Animal:
pass
class Dog(Animal):
pass
print(Dog.__mro__)
# (<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)
# Same info, different format:
print(Dog.mro())
# [<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]
For single inheritance the MRO is trivial: the class itself, its parent, the parent's parent, all the way up to object (the root of every Python class).
The MRO matters because super() walks it: super() inside a method looks at the MRO of the actual instance's class, finds the current class in it, and dispatches to the next class after that.
The Diamond Problem (and Why Python Doesn't Have It)
The classic problem from C++ era: class D inherits from B and C, both of which inherit from A. If D calls a method that exists in A, and B and C both override it, which wins? And if A's __init__ runs once via B AND once via C, you've initialized A twice — almost always a bug.
class A:
def __init__(self):
print("A.__init__")
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
class D(B, C):
def __init__(self):
print("D.__init__")
super().__init__()
D()
# D.__init__
# B.__init__
# C.__init__
# A.__init__
Notice: A's __init__ ran ONCE, not twice. Python's C3 linearization computed D's MRO as [D, B, C, A, object], and super() walked it in order. Each step delegates to the next; A appears once, so its __init__ runs once.
You can verify the MRO directly:
print([c.__name__ for c in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']
C3 Linearization: The Algorithm
Python uses the C3 linearization algorithm (introduced in Python 2.3, formalized in the Dylan programming language, mathematically described by several papers). C3 guarantees three consistency rules:
Local precedence: if you write class D(B, C), then B appears before C in D's MRO.
Monotonicity: if X appears before Y in some class's MRO, X always appears before Y in every subclass's MRO.
Consistent extended precedence: the ordering is consistent with the order of all base classes.
You don't usually need to compute MRO by hand — Python does it and exposes the result via __mro__. The mental model: local precedence first, monotonic, no surprises across subclasses. When you can't tell what the MRO will be, write a small class hierarchy and print __mro__.
When C3 fails. If your inheritance graph creates an unsolvable order (rare but possible), Python raises TypeError: Cannot create a consistent method resolution order (MRO) at class creation. The fix is almost always to simplify the inheritance, not to wrestle with the algorithm.
The Hettinger Insight: super() Calls Your Children's Ancestors
The single most quoted line about super(), from Raymond Hettinger's 2011 blog post and his 2015 PyCon talk: super() doesn't call your ancestors — it calls your children's ancestors.
The simpler version: when you write super() in a method of class B, you might assume it dispatches to B's parent. But if B is later used in a multiple-inheritance tree D(B, C), super() inside B's method might dispatch to C instead — because C comes BETWEEN B and B's lexical parent in D's MRO.
class B:
def greet(self):
print("B.greet")
super().greet() # WHERE does this go?
class C:
def greet(self):
print("C.greet")
class D(B, C):
def greet(self):
print("D.greet")
super().greet()
D().greet()
# D.greet
# B.greet
# C.greet <-- super() in B dispatched to C, NOT to B's parent
# Print MRO to confirm: [D, B, C, object]
This is why super() in a class is COOPERATIVE: it doesn't know who's next. It depends on how the class is used downstream. Hettinger's point: design cooperative classes so they all play nicely with whatever children end up between them in some future MRO.
Cooperative Multiple Inheritance Rules
The pattern that works in practice, from Hettinger's talk:
Every class in the tree calls super().__init__(), including the apparent root (which inherits from object, whose __init__ safely accepts **kwargs in modern Python).
Every class accepts **kwargs in __init__ and forwards them along to super(), so unrelated classes in the MRO can pick up the arguments they need.
Argument names don't collide across the cooperating classes, or you use keyword-only parameters explicitly.
This is the pattern Hettinger calls cooperative multiple inheritance. Each mixin handles its own keyword argument and forwards the rest. The MRO ensures every class runs once. Adding a new mixin doesn't break existing code — you just add it to the bases list.
Inspecting the MRO
Three ways to see the MRO:
print(D.__mro__) # Tuple of classes
print(D.mro()) # List of classes
print([c.__name__ for c in D.__mro__]) # Just the names
# Most readable for debugging:
import inspect
for c in inspect.getmro(D):
print(c.__name__)
When inheritance gets confusing and you're not sure which method super() will dispatch to, print the MRO. The answer is in the list.
Python 3 vs Python 2 super()
Python 2 required a verbose form: super(ClassName, self).__init__(). Python 3 introduced a no-argument form: just super().__init__(). Both work in Python 3, but the no-argument form is shorter, less error-prone, and refactor-friendly.
# Python 2 style — still works in Python 3 but fragile
class Dog(Animal):
def __init__(self, name):
super(Dog, self).__init__(name) # Hardcoded class name
# Python 3 idiomatic — preferred
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
The risk of the verbose form: rename the class or move the method to a subclass, and the hardcoded Dog becomes wrong. The no-argument form figures out the right class automatically through the method's __class__ cell. Use the no-argument form unless you're maintaining legacy Python 2 code.
Common Mistakes
Forgetting super().__init__
class Dog(Animal):
def __init__(self, name, breed):
# super().__init__(name) is missing!
self.breed = breed
rex = Dog("Rex", "Lab")
print(rex.name) # AttributeError
If the parent's __init__ sets up attributes you'll use, you MUST call super().__init__(). The compiler doesn't enforce it.
Calling super() with the wrong arguments
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, breed) # Animal doesn't take breed!
self.breed = breed
# TypeError: __init__() takes 2 positional arguments but 3 were given
Pass only the arguments the parent actually expects. For cooperative inheritance, use the **kwargs forwarding pattern shown above.
Using super() in __init_subclass__ or class methods incorrectly
These work but with slightly different rules. super() in @classmethod dispatches via cls; super() in instance methods dispatches via self.__class__. See the method types guide for the dispatch story.
Trying to skip a class in the MRO
You CAN'T skip a class. super() walks the MRO in order. If you genuinely need to bypass a class, you're probably overusing inheritance — consider composition instead. See the upcoming composition vs inheritance guide.
Real-World Example: Django ListView
Django's class-based views use this pattern heavily. Here's a simplified version of how ListView works:
class ContextMixin:
def get_context_data(self, **kwargs):
return kwargs
class TemplateResponseMixin:
template_name = None
def render_to_response(self, context):
return f"<rendered {self.template_name} with {context}>"
class View:
def dispatch(self, request):
return self.get(request)
class TemplateView(TemplateResponseMixin, ContextMixin, View):
def get(self, request):
context = self.get_context_data()
return self.render_to_response(context)
class ListView(TemplateView):
model = None
def get_context_data(self, **kwargs):
kwargs["object_list"] = self.model.objects.all()
return super().get_context_data(**kwargs)
The cooperative chain here is real: ListView.get_context_data calls super().get_context_data(**kwargs), which walks the MRO through TemplateView to ContextMixin, which returns the merged kwargs. Each class adds its piece; super() chains them together.
Frequently Asked Questions
What does super().__init__() do in Python?
super().__init__(args) calls the __init__ of the NEXT class in the Method Resolution Order, passing the arguments along. For single inheritance that's the direct parent. For multiple inheritance it's whatever class comes next in the MRO list, which is not always the lexical parent. The Hettinger insight: super() doesn't return your parent; it returns a proxy that delegates to the next class in the MRO for the actual call. You call super().__init__() in your subclass to make sure the parent's initialization runs before you add your own, so attributes set by the parent are available.
What is MRO (Method Resolution Order) in Python?
MRO is the ordered list Python uses to resolve which class's method or attribute to use when you call something on an instance. For single inheritance the MRO is trivial: subclass, parent, object. For multiple inheritance Python uses the C3 linearization algorithm to produce a single consistent ordering that respects inheritance (subclasses come before parents) and the order in which parents are listed in the class definition. You can inspect it with ClassName.__mro__ or ClassName.mro(). When you call super().method(), Python looks up the NEXT class in the MRO after the current one and dispatches there.
What is the diamond problem and how does Python solve it?
The diamond problem occurs when class D inherits from B and C, and both B and C inherit from A. If D calls super().__init__(), naively you'd call B's __init__, which calls A's __init__, AND C's __init__, which ALSO calls A's __init__ — initializing A twice. In C++ this was a real bug. Python solves it with the C3 linearization algorithm and a single MRO list per class. For our diamond, D's MRO is [D, B, C, A, object]; super() walks this list once, so A's __init__ runs exactly once. The "C3" name comes from three consistency rules the algorithm preserves: local precedence, monotonicity, and consistent extended precedence.
Should I use super() in Python 3 or super(ClassName, self)?
Use the no-argument form super() in Python 3. The verbose form super(ClassName, self) was required in Python 2 (which is end-of-life since 2020) but in Python 3, super() with no arguments works identically and is shorter, less error-prone, and refactor-friendly. The risk of the verbose form: if you rename the class or move the method to a subclass, the hardcoded ClassName becomes wrong and produces subtle bugs. Modern Python codebases use super() exclusively; reach for the verbose form only when working with legacy Python 2 code.
What did Raymond Hettinger mean by "super calls your children's ancestors"?
It's the central insight from Hettinger's PyCon 2015 "Super Considered Super!" talk. When you write super().method() inside class B, you might assume it calls B's parent's method. But if B is used as a base class in a multiple-inheritance tree D(B, C), then super() inside B's method might dispatch to C, not to B's parent — because the MRO for D puts C between B and B's actual parent. So super() doesn't dispatch to YOUR ancestors; it dispatches to the next class in the MRO of whatever class is at the bottom of the tree using B. This is what makes cooperative multiple inheritance work: each class delegates to "the next one" without knowing who that is.
The Bottom Line: super() Walks the MRO, the MRO Solves the Diamond
super() returns a proxy that delegates to the next class in the MRO — not necessarily your lexical parent. The MRO is computed by C3 linearization, exposed as __mro__, and respects the order of declared parents while preserving consistency across the inheritance graph. The diamond problem is solved because each class in the MRO appears exactly once, so cooperative super().__init__() calls run each ancestor exactly once. Use the no-arg super() form in Python 3, follow the **kwargs forwarding pattern for cooperative multiple inheritance, and when in doubt print __mro__. The next entry covers @property: getters, setters, and computed attributes. Or browse the full Python OOP guide.
Build Inheritance Reflexes Through Practice
CodeGym's Python track turns inheritance, super(), and MRO into reflex through 800+ hands-on tasks across 62 levels with real cooperative inheritance scenarios. AI validators check your subclass behavior; AI mentors explain when an MRO surprise breaks your code. First level free; full plan on the pricing page.
GO TO FULL VERSION