skip to content

Why can't @functools.singledispatch dispatch a method's argument, and what does functools.singledispatchmethod do?

level: middleimportance: should knowfreq 28%

answer

  1. The receiver gets in the way
  2. Plain singledispatch sees self first
  3. It fails silently, always the fallback
  4. A descriptor that skips the receiver
  5. Outermost when stacked with classmethod

basics

~20 s

Inside a class body, singledispatch still dispatches on the first positional argument, which is self, so every call on one class picks the same implementation. functools.singledispatchmethod, added in 3.8, is a descriptor that dispatches on the first argument after self.

solid answer

~40 s

`@functools.singledispatch` has no idea it is decorating a method. It reads the class of the first positional argument, which for a bound call is the instance, so every call through one class resolves to the implementation registered for that class — normally none, meaning the fallback runs every time. It fails silently rather than loudly, which is what makes it a good interview question. `functools.singledispatchmethod` (3.8+) fixes it by being a *descriptor*: its `__get__` returns a bound callable that skips `self` and dispatches on the next positional argument. Registration works the same way inside the class body — `@render.register` with an annotated parameter, or `@render.register(int)`. When combining it with `classmethod` or `staticmethod`, `singledispatchmethod` must be the outermost decorator so that the `register` attribute is reachable.

code

python · 17 lines
python
import functools

class Report:
    @functools.singledispatchmethod
    def render(self, value):
        return f"raw {value!r}"

    @render.register
    def _(self, value: int):
        return f"int {value}"

    @render.register
    def _(self, value: list):
        return f"list of {len(value)}"

r = Report()
print(r.render(3), r.render([1, 2]), r.render(None), sep=" | ")

go deeper

for a junior

Recall the rule: inside a class, use functools.singledispatchmethod rather than functools.singledispatch, because the plain decorator would look at self. Know that implementations still take self first and the dispatched value second.

for a middle

Explain the mechanism: the plain decorator resolves on the receiver's class and quietly always picks the fallback, while singledispatchmethod is a descriptor whose attribute access returns a callable that dispatches on the argument after self.

for a senior

Show you would catch this in review, since the failure is silent rather than an exception. Know the decorator ordering with classmethod, and that a subclass registering on an inherited generic method mutates the base class's registry.

for a principal

Own the boundary: a generic method spreads one operation's implementations across modules, so decide when that extensibility beats plain methods on classes you control, and set the convention for where registrations live and how they are proven to be imported.

## The silent failure The generic-function wrapper built by `@functools.singledispatch` looks at exactly one thing: the class of the first positional argument. It has no concept of a receiver. Put it on a method and the first positional argument is the instance: ```python import functools class Broken: @functools.singledispatch def render(self, value): return "fallback" @render.register def _(self, value: int): return "int" Broken().render(3) # 'fallback' ``` Nothing raises. The registration for `int` is real and sits in the registry, but resolution is performed against the class of the instance — `Broken` — which is not registered, so the walk lands on `object` and the fallback runs. Every call on every instance of that class picks the same implementation, and the only symptom is that your carefully registered branches never execute. Registering the *class* would work, but that dispatches on the receiver, which is what ordinary method overriding already does. ## What singledispatchmethod adds `functools.singledispatchmethod`, added in 3.8, wraps the same registry machinery in a **descriptor** — an object that defines `__get__` and therefore gets a say in what attribute access on an instance produces. Accessing `instance.render` calls `__get__`, which returns a small bound callable that resolves the implementation from the class of the *next* positional argument, then calls it with the instance reinstated in front. So the receiver is passed through untouched, and dispatch happens on the argument you actually care about. ```python import functools class Report: @functools.singledispatchmethod def render(self, value): return f"raw {value!r}" @render.register def _(self, value: int): return f"int {value}" @render.register def _(self, value: list): return f"list of {len(value)}" ``` Every implementation still takes `self` first and the dispatched value second, and the annotation that drives registration is on that second parameter. Resolution rules are identical to the plain function case: the nearest registered ancestor of the argument's class wins, registration order is irrelevant, and the decorated body is the `object` fallback. ## Stacking with classmethod and staticmethod Both combinations work, with one hard ordering rule: `singledispatchmethod` must be the **outermost** decorator, because the registration API lives on it and the inner decorator would otherwise hide it. Applied to a class method, dispatch is on the first argument after `cls`: ```python import functools class Loader: @functools.singledispatchmethod @classmethod def load(cls, source): return f"generic {cls.__name__}" @load.register @classmethod def _(cls, source: str): return f"{cls.__name__} from {source}" ``` The inner decorator has to be repeated on each registered implementation, not just on the base definition. ## The registry is shared with subclasses The descriptor object — and the registry inside it — is an attribute of the class where it was defined. A subclass that registers an extra implementation on the inherited generic method is mutating that same shared registry, so the base class and every sibling subclass see the new implementation too: ```python import functools class Base: @functools.singledispatchmethod def render(self, value): return "base-fallback" class Sub(Base): @Base.render.register def _(self, value: float): return "sub-float" Base().render(1.0) # 'sub-float' — the base class changed too ``` That is rarely what a reader expects. If a subclass needs its own set of implementations, define a fresh `singledispatchmethod` in the subclass — which shadows the inherited attribute and starts with an empty registry — and delegate to the base implementation from its fallback if you still want the old behaviour. ## When to reach for it A generic *method* is worth the machinery when the operation belongs to an object with state — a renderer holding configuration, a differ holding options — but must branch on the class of the thing it is handed, and that set of classes is open. If the receiver carries no state, a module-level `singledispatch` function is simpler. And if you own the classes being dispatched on, an ordinary method on each of those classes remains the most discoverable answer: dispatch machinery earns its place mainly for classes you cannot or should not modify. ## Introspection is thinner here A plain generic function exposes both a registry mapping and a way to resolve a class to its implementation without calling it. The bound callable you get from attribute access on a generic *method* is thinner: it carries the registration API and nothing else, so there is no registry or resolution helper on `instance.render` or on `Report.render`. To inspect coverage — in a test that proves a registration landed, for instance — reach the descriptor object itself out of the class dictionary and use its dispatcher, which is an ordinary generic function with the full introspection surface. It is a small wrinkle, but one that surprises people who try to write the same coverage assertion they use for module-level generic functions.

  • How do you combine singledispatchmethod with classmethod?
    Put singledispatchmethod outermost and classmethod directly under it, on the base definition and on every registered implementation. Dispatch then happens on the first argument after cls. The ordering is not cosmetic: the registration API lives on the outer object, so inverting the two decorators leaves you with no register attribute to use.
  • What happens if a subclass registers an extra implementation on an inherited generic method?
    The descriptor and its registry live on the class where the method was defined, so the registration is shared: the base class and every sibling subclass start selecting the new implementation too. If a subclass needs isolated behaviour, define a new singledispatchmethod that shadows the inherited one and delegate to the base from its fallback.
  • When is a generic method the wrong tool compared with an ordinary method?
    When you own the classes being dispatched on. Putting the behaviour on each class keeps it next to the data and is discoverable from the class itself, with no registry to inspect. A generic method earns its keep when the receiver holds state and the dispatched classes are ones you cannot or should not modify.

saying these in an interview costs you the question

  • Says plain singledispatch works on methods unchanged
  • Puts classmethod outside singledispatchmethod
  • Expects dispatch on the receiver's class to be useful
  • Assumes each subclass gets its own registry
  • Thinks the silent fallback is a registration syntax error

context