skip to content

type and Metaclasses

A class is an object created by its metaclass — normally type — and type(name, bases, namespace) builds one at runtime. Interviewers ask when __init_subclass__ or a class decorator is the better tool instead.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What does the three-argument `type(name, bases, namespace)` call create in Python?

level: middleimportance: must knowfreq 55%

answer

  1. One builtin, two unrelated jobs
  2. Three arguments do not inspect anything
  3. name, bases, namespace
  4. The class statement is sugar over this
  5. type(type) is type

basics

~10 s

It creates and returns a brand-new class object at runtime. type('Duck', (Bird,), {'legs': 2}) produces the same kind of object a class Duck(Bird) statement would. The one-argument form, type(obj), instead reports an object's class.

solid answer

~40 s

`type` does two unrelated jobs. With one argument it is introspection: `type(obj)` returns the class the object is an instance of. With three arguments it is a constructor: `type(name, bases, namespace)` builds a new class object, where `name` becomes `__name__`, `bases` is a **tuple** of base classes, and `namespace` is a mapping whose entries become class attributes. That second form is what a `class` statement ultimately reaches: the compiler executes the class body to produce a namespace mapping, works out the metaclass, and calls it. Because a class is itself an object, its class is `type` — and `type(type) is type`. Use the three-argument form when the shape of a class is only known at runtime; prefer a `class` statement otherwise, since dynamically built classes are invisible to type checkers and IDEs.

code

python · 9 lines
python
def quack(self):
    return f"{self.sound}!"


Duck = type("Duck", (object,), {"sound": "quack", "quack": quack})

d = Duck()
print(Duck.__name__, Duck.__mro__[1].__name__, d.quack())
print(type(d) is Duck, isinstance(Duck, type))

go deeper

for a junior

Recall that type(obj) tells you an object's class and that type(type) is type because classes are objects too. Knowing the three-argument form exists, even without using it, already puts you ahead at this level.

for a middle

Be ready to write the three-argument call from memory and say what each argument is, including that bases must be a tuple. Explain that a class statement compiles to essentially the same call, and name one honest use for building a class at runtime.

for a senior

Show the judgement: dynamically generated classes defeat type checkers, IDE navigation and pickling, so justify each one. Know that types.new_class — not bare type(...) — is what you call when a caller supplies the bases.

for a principal

Own the policy question of how much of your codebase is generated at import time. Runtime class generation trades authoring convenience for debuggability, tooling support and cold-start cost, and that trade should be a documented decision, not an individual's habit.

`type` is two callables wearing one name, and this question is really asking whether you know the second one. ## One argument: introspection `type(obj)` returns the class the object is an instance of — `type(3)` is `<class 'int'>`, `type("a")` is `<class 'str'>`. It reads the object's real class, which is also normally reachable as `obj.__class__`. The two can disagree: `__class__` is an ordinary attribute lookup that a class can override or that instrumentation can reassign, while `type(obj)` reports what the object actually is. That is why library code that means *exactly this class, not a subclass* writes `type(x) is Foo`, and code that means *this class or any subclass* writes `isinstance(x, Foo)`. ## Three arguments: construction `type(name, bases, namespace)` creates and returns a new class object. * `name` — a `str` that becomes the class's `__name__`, and by default its `__qualname__`. * `bases` — a **tuple** of base classes. Passing a list raises `TypeError`. An empty tuple still yields a class whose base is `object`. * `namespace` — a mapping whose keys become the class's attributes: methods, class variables, `__doc__`, `__slots__`, and so on. The result is not a second-class citizen. It is exactly the object a `class` statement produces, with a working `__mro__`, working inheritance, working descriptors. ## Why the class statement is the same machinery A `class` statement is compiled into: build a function from the class body, execute it to collect a namespace mapping, determine which metaclass to use, then call that metaclass with `(name, bases, namespace)`. Since the default metaclass is `type`, `class Duck(Bird): legs = 2` lands on the same call you can write by hand. Three differences survive, and they are the interesting part of the answer: 1. The `class` statement **derives** the metaclass from the bases (and from a `metaclass=` keyword). Calling `type(...)` directly hard-codes `type`, so if a base uses a custom metaclass you get a metaclass conflict `TypeError` — or, worse, you sidestep behaviour the author intended. 2. The `class` statement calls the metaclass's `__prepare__` first to obtain the mapping the body executes in. A direct `type(...)` call has no body to execute, so nothing prepares anything. 3. The compiler fills `__qualname__` and `__module__` from the surrounding source. A dynamically created class takes `__qualname__` from the `name` you passed, and `__module__` from the calling frame's `__name__` — so a class built inside a function does not report the nested qualname a `class` statement there would. `types.new_class(name, bases, kwds, exec_body)` and `types.prepare_class` exist precisely to run the *full* protocol from code, including metaclass resolution and `__prepare__`. Reach for those when the bases are supplied by a caller and might carry their own metaclass; reach for bare `type(...)` only when you own all three arguments. ## Classes are objects, and `type` closes the loop Every class is an instance of some class — its metaclass — and the default is `type`. `type` is itself a class, so it too has a class: itself. `type(type) is type` terminates the regress. `type` is also a subclass of `object`, while `object` is an instance of `type`; the two are bootstrapped together in the interpreter and this apparent circularity is deliberate, not a bug. ## Where the three-argument form actually earns its place Building classes from data known only at runtime: record types generated from a schema or a configuration file, adapter classes generated per external resource, throwaway classes in tests, and — most commonly — inside a metaclass's own `__new__`, which delegates to `super().__new__(mcls, name, bases, namespace)` after adjusting the namespace. ## Pitfalls worth naming in an interview * `bases` must be a tuple; a list is a `TypeError`. * The namespace is copied into the class's own mapping, so mutating the dict you passed afterwards does not change the class — use `setattr` for that. * Dynamically created classes generally cannot be pickled unless they are also bound at module level under the same `__name__`, because pickle stores classes by qualified name, not by value. * Static tooling — type checkers, IDEs, refactoring, autocomplete — cannot see attributes that only exist at runtime. That cost is the strongest argument for writing a `class` statement whenever the shape is actually known at authoring time. The short version an interviewer wants to hear: *a class is an object; `type` is the class of classes; calling it with three arguments is how classes get made, and the `class` statement is sugar over exactly that call.*

  • When would you use `types.new_class` instead of calling `type(name, bases, namespace)` directly?
    Whenever the bases are not fully under your control. `types.new_class` runs the whole class-creation protocol: it resolves the most derived metaclass from the bases and any keyword arguments, calls that metaclass's `__prepare__` to get the body namespace, executes your body callback into it, then calls the metaclass. A bare `type(...)` call hard-codes `type`, so a base carrying a custom metaclass either raises a metaclass conflict or quietly loses that metaclass's behaviour.
  • Which attributes differ between a class built with `type(...)` and the equivalent `class` statement?
    Mainly the ones the compiler fills in from source. `__qualname__` defaults to the plain `name` you passed rather than a dotted nested name, `__module__` comes from the calling frame's `__name__`, and there is no `__doc__` unless you put one in the namespace. Everything else — `__mro__`, inheritance, descriptors, `__slots__` — behaves identically, because it genuinely is the same kind of object.
  • Why can a dynamically created class break pickling?
    `pickle` serialises a class by reference — module name plus qualified name — not by value. If the class was built inside a function and never bound at module level under that exact name, unpickling cannot look it up and fails. The fix is to assign the generated class to a module-level name matching its `__name__`, or to give instances a `__reduce__` that rebuilds them from data.

A blueprint is itself a manufactured object. type is the machine that stamps out blueprints, and calling it with one argument asks a finished part which blueprint made it.

saying these in an interview costs you the question

  • Says `type` can only report a class, never create one
  • Thinks a class statement produces something different from `type(...)`
  • Claims classes are not objects in Python
  • Passes the bases as a list instead of a tuple
  • Confuses the namespace mapping with an instance `__dict__`
  • Uses runtime class generation where a class statement would do

context

open as a page

When a class uses `metaclass=Meta`, in what order do `__prepare__`, `__new__` and `__init__` run?

level: middleimportance: should knowfreq 40%

basics

~20 s

Meta.__prepare__ runs first and returns the mapping the class body executes into. The body then runs, and the filled mapping is handed to Meta.__new__, which creates the class object, and finally to Meta.__init__, which configures it.

open as a page

How do you decide between a metaclass and a class decorator for enforcing a rule across many classes?

level: principalimportance: should knowfreq 35%

basics

~20 s

Decide on reach and cost. A metaclass is inherited, so it governs every future subclass and constrains inheritance; a class decorator touches only the class it is written on, but is visible and composable. Default to the decorator.

open as a page

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

level: seniorimportance: nice to knowfreq 25%

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.

open as a page