What object does a Python class decorator receive, and what does the class name end up bound to?
answer
- Same @ rule as a function
- The class body finishes first
- One call, one rebinding of the name
- C = deco(C)
- The return value is never checked
basics
~20 sA class decorator is called with the finished class object, after the class body has already executed, and whatever it returns is bound to the class name. Most decorators mutate the class and return the same object.
solid answer
~40 s`@deco` above `class C:` is sugar for running the class body, building the class, then executing `C = deco(C)`. The decorator therefore receives a fully created class object -- methods, `__name__`, `__qualname__`, `__module__` and bases all in place -- and cannot influence how that class was built; that is a metaclass's job. Whatever the decorator returns is bound to the name, and Python does not check that it is a class at all: returning a function or an instance is legal and silently breaks `isinstance` checks and subclassing. The idiomatic form is to mutate the class (add attributes, register it, wrap its methods) and `return cls`. Decoration happens once, when the class statement executes, not per instance.
code
python · 12 linesdef tagged(cls):
cls.tag = cls.__name__.lower()
return cls
@tagged
class Segment:
pass
print(Segment.tag) # segment
print(type(Segment)) # <class 'type'>go deeper
Be ready to write the longhand C = deco(C) on a whiteboard and to say that the decorator gets the finished class, not an instance. Remember return cls -- omitting it is the mistake interviewers watch for.
Explain the ordering precisely: body executes, metaclass builds the class, decorator is called, name is bound. Know that the return value is unchecked and that returning something other than a class breaks isinstance and subclassing.
Show the operational consequences: decoration is import-time work that runs once, so registration and validation belong here, while anything slow or order-dependent does not. Be able to explain why a rebuilt class orphans earlier references to the original.
Own the API-design angle: a decorator that returns a different object than it received is a contract change for every caller and every type checker, so treat class replacement as a last resort and prefer mutate-and-return in shared code.
### The transform, stated exactly The `@` on a class is the same `@` as on a function; only the decorated object differs. When the interpreter reaches ```python @deco class C: ... ``` it executes the class body to completion, hands the resulting namespace to the metaclass (`type`, unless the class says otherwise), gets back a finished class object, and *only then* calls `deco` with that object. The name `C` is then bound to the return value. The equivalent longhand is: ```python class C: ... C = deco(C) ``` That is the entire mechanism. Everything else about class decorators follows from it. ### Consequence 1: the class is already finished By the time the decorator runs, the class exists. Its methods are attributes in `cls.__dict__`, `cls.__name__` and `cls.__qualname__` are set, its bases are recorded and its method resolution order is computed. A class decorator can therefore *inspect and mutate*, but it cannot change how the class was constructed -- it cannot alter which metaclass ran, and it cannot retroactively add `__slots__`, because slot descriptors have to exist before the class object is laid out. Anything that must intervene *during* creation belongs to a metaclass or to `__init_subclass__` on a base class. ### Consequence 2: mutate and return `cls` is the normal shape The common body is three lines: do something with the class, then hand it back. ```python def tagged(cls): cls.tag = cls.__name__.lower() return cls ``` Mutation is done with plain attribute assignment or `setattr`, and it affects the one class object every future instance and subclass will look through. Typical work: attaching a computed attribute, appending the class to a registry, adding generated methods, or validating that required methods exist and raising at import time if they do not. ### Consequence 3: forgetting `return cls` is the classic bug A decorator with no explicit `return` returns `None`, so the class name is bound to `None`. The class statement appears to succeed and the failure surfaces later as `TypeError: 'NoneType' object is not callable` on the first instantiation. Nothing checks the return type, because a decorator is just a call. ### Consequence 4: returning a different object is legal Returning a function, an instance, or an entirely new class is permitted and occasionally deliberate, but it changes what the name means. Callers who wrote `isinstance(x, C)` or `class Sub(C):` are now talking about the replacement, and tools that follow the source will disagree with the runtime. The stdlib does this in exactly one well-known place: `@dataclasses.dataclass(slots=True)`, added in 3.10, must build a *new* class because slots cannot be added after the fact -- the plain `@dataclasses.dataclass` form returns the same class object it was given. Rebuilding a class also silently discards anything that captured the original, such as an already-registered reference or a closure over `__class__`. ### Decoration runs once, at class-creation time For a module-level class the decorator body executes while the defining module is imported, exactly once, no matter how many instances are made. That makes class decorators a natural place for registration and import-time validation, and a bad place for anything expensive or order-dependent. It also means subclasses do **not** re-run it: decorating a base class runs the decorator on that base only, and subclasses merely inherit whatever attributes it left behind. ### Stdlib class decorators to name in an interview `@dataclasses.dataclass`, `@functools.total_ordering`, `@enum.unique` and `@typing.runtime_checkable` are all class decorators, and all but the `slots=True` dataclass form return the same class object after mutating it. Being able to say *"`@enum.unique` checks for duplicate values and returns the same enum class"* demonstrates that you understand the shape rather than having memorised one example. ### The mental checklist Given any class decorator, four questions settle its behaviour: what does it receive (the finished class), when does it run (once, at class-statement time), what does it return (usually the same class), and does the name still mean what the reader thinks it means (only if it returned the same class). Those four cover almost every real bug in this area.
- What happens if the decorator has no return statement?It returns `None`, so the class name is bound to `None`. The class statement itself looks fine; the error appears later, usually as `TypeError: 'NoneType' object is not callable` at the first instantiation, or as an `AttributeError` on the first attribute access. Forgetting `return cls` is the single most common class-decorator bug.
- Can a class decorator change which metaclass built the class?No. The metaclass has already run and produced the class object before the decorator is called, so the decorator can only inspect or mutate the result. It could construct a brand-new class with a different metaclass and return that instead, but the original class was still created first, and anything that captured it keeps the old object.
- Does decorating a base class also decorate its subclasses?No. The decorator runs once, on the class statement it sits above. A subclass inherits whatever attributes the decorator left on the base through normal attribute lookup, but the decorator body never runs again, so per-class work such as registration or validation silently skips every subclass unless each one is decorated too.
It is a stamp applied to a passport that is already printed: the decorator can add pages or a mark, but it cannot change how the passport was manufactured.
saying these in an interview costs you the question
- Says the decorator receives an instance of the class
- Thinks it runs once per instantiation
- Omits return cls and expects the class to survive
- Believes Python validates that a class is returned
- Claims a class decorator can add __slots__ afterwards
- Assumes subclasses re-run the decorator automatically