skip to content

What does a metaclass's `__call__` control that a class's own `__new__` cannot?

level: seniorimportance: nice to knowfreq 25%

answer

  1. Calling a class is calling an object
  2. One level up from `__new__`
  3. Who decides whether `__init__` runs at all
  4. The cached-instance re-initialisation trap
  5. type(cls).__call__ owns the whole sequence

basics

~20 s

It controls the whole act of calling the class: Duck() runs type(Duck).__call__, so a metaclass can return a cached object without __new__ or __init__ running at all. A cached instance returned from __new__ still gets __init__ re-run.

solid answer

~40 s

Calling a class is an ordinary call on an object whose class is the metaclass, so `Timetable(...)` invokes `type(Timetable).__call__(Timetable, ...)`. The default `type.__call__` runs `cls.__new__`, and then runs `cls.__init__` on the result if that result is an instance of `cls`. Overriding `__call__` on a metaclass replaces that whole sequence: you can validate arguments before anything is allocated, return an interned or cached object without ever touching `__new__` or `__init__`, or post-process a fully initialised instance. A class's own `__new__` cannot do those things cleanly — most notably, returning an existing instance from `__new__` still causes `__init__` to run again on it, silently re-initialising shared state. That re-initialisation bug is the usual reason people move up to the metaclass.

code

python · 22 lines
python
class Cached(type):
    def __call__(cls, *args, **kwargs):
        cache = cls.__dict__.get("_instances")
        if cache is None:
            cache = {}
            setattr(cls, "_instances", cache)
        key = (args, tuple(sorted(kwargs.items())))
        if key not in cache:
            cache[key] = super().__call__(*args, **kwargs)
        return cache[key]


class Timetable(metaclass=Cached):
    def __init__(self, carrier, revision):
        print("parsing", carrier, revision)
        self.carrier = carrier
        self.revision = revision


a = Timetable("ZZ", 7)
b = Timetable("ZZ", 7)
print(a is b)

go deeper

for a junior

Know that calling a class produces an instance and that __init__ is what sets it up. The idea that the call itself can be intercepted is beyond what is expected here, so focus on __new__ versus __init__ first.

for a middle

Be able to say that Klass() dispatches to the metaclass, not to Klass.__call__, and explain the default sequence: __new__, then __init__ if the result is an instance of the class. That single fact explains most surprising behaviour in this area.

for a senior

Show that you have hit the re-initialisation trap and know both fixes. Be ready to weigh a caching metaclass against a plain factory function, and to name the leak risk of a class-level cache in a process that runs for weeks.

for a principal

Frame it as an API-honesty decision: interception that a call site cannot see is a tax on every future reader and defeats static tooling. Decide when unavoidable interception is genuinely required, and require an explicit, visible alternative otherwise.

The mechanism follows from one fact: **a class is an object, and calling an object calls its type's `__call__`**. So `Timetable("ZZ", 7)` is `type(Timetable).__call__(Timetable, "ZZ", 7)`. If `Timetable` has an ordinary metaclass, that is `type.__call__`, whose behaviour is roughly: 1. call `cls.__new__(cls, *args, **kwargs)` to get an object; 2. if the returned object is an instance of `cls`, call `type(obj).__init__(obj, *args, **kwargs)`; 3. return the object. Everything a metaclass `__call__` can do that `__new__` cannot follows from owning that whole sequence rather than one step inside it. ## The re-initialisation trap The classic interning attempt puts a cache in `__new__`: ```python def __new__(cls, key): if key in cls._seen: return cls._seen[key] ... ``` It returns the shared object — and then step 2 fires, because the returned object *is* an instance of `cls`, so `__init__` runs on it again with the new arguments. Any state built up on that object is silently reset on every subsequent call. Worse, the arguments a caller passes the second time may differ, so the shared object quietly mutates under everyone holding it. Move the cache into the metaclass's `__call__` and you decide whether `super().__call__(...)` — the `__new__`/`__init__` pair — runs at all. ## Where this actually pays off Consider a flight-schedule differ that parses published timetables and compares revisions. Parsing one is expensive: a cold start of about 45 seconds before the first comparison can be produced. The comparison rules ask for `Timetable(carrier, revision)` in dozens of places, and each request must yield the same parsed object. A caching metaclass keys instances on the constructor arguments and returns the existing one, so parsing happens once per `(carrier, revision)` regardless of how many call sites ask. It also gives you a place to *canonicalise* before caching. A clock-skew artefact — the same published revision arriving with timestamps a second or two apart from two upstream feeds — would otherwise produce two cache keys and two parsed objects that then compare as different. Normalising the key inside `__call__`, before any object is allocated, is the natural fix; `__new__` cannot get in front of that because by then the call has already been dispatched to the class. Other legitimate uses: rejecting invalid constructor arguments with a domain error before allocation; returning an instance of a *different* class (a factory that picks a subclass by argument); attaching cross-cutting bookkeeping after `__init__` returns, in one place, for every subclass. ## Cost, and why this is a differentiator rather than a gate A metaclass `__call__` is invisible at the call site. A reader sees `Timetable("ZZ", 7)` and reasonably assumes a fresh object. Debuggers step past it. Type checkers model the constructor from `__init__`, so a `__call__` that returns something else is a lie to static tooling. And the metaclass is inherited: every subclass gets the interception, whether or not it wants it. The caching itself has an operational cost people forget. A dict on the class holds strong references for the life of the process, so it is an unbounded leak keyed by whatever arguments callers happened to pass. If the population of keys is unbounded, use weak references, an explicit bounded cache, or an explicit factory function that callers can see. Cache keys must also be hashable and canonical, which rules the pattern out for constructors taking mutable arguments. ## The honest alternative Much of what people build metaclass `__call__` for is better expressed as a plain module-level factory function — `get_timetable(carrier, revision)` — with an ordinary cache inside it. It is visible, testable, mockable, and it lets a caller who genuinely wants a fresh object still call the class directly. Reach for `__call__` when the interception must be *unavoidable* — because third-party code calls the class directly and you cannot change those call sites — and say so when you propose it. The interviewer is checking two things: that you know `Klass()` dispatches to the metaclass rather than to `Klass.__call__`, and that you know why the naive `__new__` cache re-runs `__init__`. Everything else is judgement about whether the magic is worth it.

  • Why does returning an already-cached instance from `__new__` still trigger `__init__`?
    Because `type.__call__` calls `__init__` whenever `__new__` returned an object that is an instance of the class being called — it has no way to know the object is old. The usual workarounds are a guard flag checked at the top of `__init__`, or moving the caching up into the metaclass's `__call__`, where you control whether the `__new__`/`__init__` pair runs at all.
  • What are the operational risks of a caching metaclass in a long-running process?
    The cache normally lives on the class, so it holds strong references for the process lifetime and grows without bound if the key space is open. It also makes object identity depend on call history, which surprises tests and makes reasoning about mutation harder. Bound the cache, or key it weakly, and prefer an explicit factory function when the interception does not have to be unavoidable.
  • Can a metaclass's `__call__` return an object that is not an instance of the class?
    Yes — it is an ordinary method and whatever it returns is what the caller gets, so it can dispatch to a subclass or return a proxy. That is powerful and dishonest in equal measure: static type checkers model the constructor from `__init__` and will believe the caller got the class they named, so a returned substitute has to be a genuine drop-in or the deception shows up as production bugs.

__new__ is a factory worker who can hand you a part off the shelf; the metaclass's __call__ is the front desk, which can answer your request without ever sending it to the factory floor.

saying these in an interview costs you the question

  • Thinks `Duck()` calls `Duck.__call__`
  • Says a metaclass `__call__` runs when the class is created
  • Believes a cached object from `__new__` skips `__init__`
  • Reaches for a metaclass where a factory function suffices
  • Assumes the instance cache is reclaimed automatically
  • Confuses controlling instance creation with controlling class creation

context