How does zero-argument super() know its class, and when does that fail?
answer
- The compiler adds something to the function
- A hidden closure cell plus a frame slot
- co_freevars shows one free variable
- Two distinct RuntimeError messages
- Nested helpers and staticmethods lack a receiver
basics
~20 sThe compiler adds a hidden class closure cell to any function in a class body that mentions super or class, and zero-argument super() reads that cell plus the frame's first positional argument. Missing either raises RuntimeError.
solid answer
~50 sZero-argument `super()` is compiler support with two ingredients. Any function defined in a class body that mentions `super` or `__class__` gets an implicit `__class__` closure cell holding the class object the class statement produced — visible as `co_freevars == ('__class__',)`. At the call, `super()` reads that cell for the defining class and takes the receiver from the first positional slot of the running frame. Both must be present: a function outside any class body raises `RuntimeError: super(): __class__ cell not found`, while a nested helper or a `staticmethod` — which has the cell but no first argument — raises `RuntimeError: super(): no arguments`. In those places you write the explicit form, `super(C, self)` in an instance method or `super(C, cls)` in a `classmethod`, where the first argument names the *defining* class and the second is the receiver.
code
pycon · 10 lines>>> class A:
... def who(self): return "A"
...
>>> class B(A):
... def who(self): return "B->" + super().who()
...
>>> B.who.__code__.co_freevars
('__class__',)
>>> B.who.__closure__[0].cell_contents
<class '__main__.B'>go deeper
You mainly need to know that super() takes no arguments in modern Python and only works inside a method of a class. Recognising the RuntimeError it raises elsewhere, and not copying the two-argument Python 2 spelling, is enough here.
Explain both ingredients: the implicit class closure cell the compiler adds, and the first positional argument read from the frame. Be able to name the two failure sites — a plain function, and a nested helper or staticmethod — and give the explicit fallback for each.
Show why the cell is not just sugar: a class decorator that returns a new class, or a class built in a factory, makes a hand-written class name resolve to something else and turns the call into unbounded recursion. That is the argument for standardising on the zero-argument form.
Set the house rule and the lint that enforces it. Decide whether explicit super is ever allowed in your codebase, and if so require a comment justifying the starting class, because a wrong first argument is a defect that surfaces only once someone subclasses your class.
### Two ingredients, both invisible `super()` with no arguments is not a plain function that inspects the stack by luck. It relies on a compile-time transformation plus a run-time frame read. **The `__class__` cell.** When the compiler sees the name `super` or `__class__` used inside a function defined in a class body, it makes that function a closure over an implicit cell variable named `__class__`, and arranges for the class statement to fill that cell with the class object it creates. The evidence is directly inspectable: ```python class A: def who(self): return "A" class B(A): def who(self): return "B->" + super().who() print(B.who.__code__.co_freevars) # ('__class__',) print(B.who.__closure__[0].cell_contents) # <class '__main__.B'> ``` **The first positional argument.** The receiver is not stored anywhere; `super()` reads slot zero of the currently executing frame. In a normal instance method that slot holds `self`; in a `classmethod` it holds `cls`. ### The two failures, and their exact messages Remove either ingredient and the call raises at run time, not at import time: - A function that is not lexically inside a class body has no cell: `RuntimeError: super(): __class__ cell not found`. - A function that has the cell but no first positional argument raises `RuntimeError: super(): no arguments`. Two ordinary constructs hit this: a `staticmethod`, which takes no receiver at all, and a helper function nested inside a method, whose own frame starts empty even though the cell is reachable through the enclosing scope. In both cases the fix is the explicit form. Inside a nested helper you already have `self` from the enclosing scope, so `super(B, self).who()` works; a `staticmethod` that needs the chain wants to be a `classmethod` taking `cls`, after which zero-argument `super()` works again. ### What the two arguments mean `super(C, obj)` reads as "start after `C` in `type(obj)`'s order, and bind `obj`". The first argument is the class the code lives in, never the class you want to reach — that is the single most common misreading. The second may be an instance, giving bound methods, or a class, giving the `classmethod` form `super(C, cls)`. Python validates the pair: if the second argument is neither an instance of the first nor a subclass of it, you get a `TypeError` saying exactly that, which is a useful signal that the two arguments were swapped. ### Why the cell beats writing the class name It is tempting to think `super(B, self)` and `super()` differ only in typing. They differ in what `B` means. The cell holds the class object the class statement built. The hand-written name is a global lookup performed at call time, and any decorator that returns a *different* class rebinds that global: ```python def wrap(cls): class Wrapper(cls): pass return Wrapper @wrap class B(A): def who(self): return "B" + super(B, self).who() ``` Here the global `B` is `Wrapper`, a *subclass* of the original class, so the search starts after `Wrapper` — which lands back on the original `who` and recurses until `RecursionError`. Written as `super()`, the same class works, because the cell still points at the class the body created. The same argument applies to class renaming, to classes built inside factory functions, and to any code where the name and the object can drift apart. ### One nuance worth knowing A class nested inside another class body gets its own cell for its own methods, so `super()` written in the inner class refers to the inner class, not the outer one. And since comprehension inlining in Python 3.12, a bare `super()` inside a list, set or dict comprehension in a method now works — the comprehension runs in the method's own frame — whereas a generator expression still creates its own frame whose slot zero holds the iterator being consumed, so a bare `super()` there fails. ### When you still write it out Explicit `super` has real uses beyond the failure cases: calling a chain from outside any class (a debugging helper, a migration script), or deliberately starting the search from a class other than the current one, which is rare and deserves a comment. Everywhere else, zero-argument `super()` is shorter, survives renames and decorators, and cannot get its own first argument wrong.
- What exactly do the two arguments of super(C, obj) mean, and which one is most often wrong?The first names the class the calling code is defined in — the position to start *after* — and the second is the receiver to bind. The first is the one people get wrong: they pass the class they want to reach, or the runtime type of the object, instead of the class they are writing in. Python checks the pair, so a second argument that is neither an instance nor a subclass of the first raises a TypeError.
- Why does a staticmethod defined in a class body still fail with zero-argument super()?The cell is present — it is added on the mention of `super` in any class-body function — but the receiver is not. A `staticmethod` frame has no first positional argument to read, so the call raises `RuntimeError: super(): no arguments`. If the method genuinely needs the chain it should be a `classmethod`, whose `cls` fills that slot and lets zero-argument `super()` work.
- Does bare super() work inside a comprehension or a generator expression in a method?Since comprehension inlining in Python 3.12, a list, set or dict comprehension runs in the enclosing method's own frame, so a bare `super()` inside one works; on 3.11 and earlier it raised `RuntimeError`. A generator expression still gets its own frame, and slot zero there holds the iterator being consumed, so a bare `super()` raises a `TypeError` saying that object is not an instance of the class. Hoist the call out, or use the explicit form.
Zero-argument super() is like a return address stamped into the envelope at the factory: the class writes its own identity into the method, so relabelling the door later cannot misdirect the reply.
saying these in an interview costs you the question
- Claims super() inspects the call stack to find the class
- Thinks the first argument is the class you want to reach
- Says super() works anywhere, including plain functions
- Uses super(type(self), self) as the explicit spelling
- Cannot explain why a staticmethod cannot use super()
- Believes super(B, self) and super() are always identical