Metaclasses & Introspection
Metaclasses, introspection, and reflection — controlling class creation.
8 questions
JuniorTheoryVery commonWhat is a metaclass in Python?
What is a metaclass in Python?
A metaclass is the class of a class — the thing that creates class objects, just as a class creates instances. The default metaclass is type; you choose another with class Foo(metaclass=Meta).
Common mistakes
- ✗Confusing a metaclass (class of a class) with a base class used in ordinary inheritance
- ✗Forgetting that
typeis itself the default metaclass for every class - ✗Thinking instances of a class are metaclasses rather than its class object
Follow-up questions
- →How do you specify a custom metaclass for a class definition?
- →What does
type(SomeClass)return for an ordinary class?
JuniorTheoryCommonIn what sense are classes themselves objects in Python?
In what sense are classes themselves objects in Python?
A class is a first-class object: an instance of type, its metaclass. You can assign it to variables, pass it to functions, and build it at runtime. type(Foo) is type, and Foo.__class__ is its metaclass.
Common mistakes
- ✗Believing
type(Foo)returnsobjectinstead of the class's metaclasstype - ✗Assuming a class cannot be assigned to a variable or passed as an argument
- ✗Thinking classes exist only at compile time and never as runtime objects
Follow-up questions
- →What is the difference between
type(Foo)andFoo.__bases__? - →How can you create a class dynamically without a
classstatement?
JuniorTheoryCommonWhat is introspection in Python?
What is introspection in Python?
Introspection is a program examining an object's type and properties at runtime without changing it: type(obj), isinstance(obj, C), dir(obj), hasattr(obj, n), plus obj.__dict__ and obj.__class__. It reads, never mutates.
Common mistakes
- ✗Believing introspection mutates the object — it only reads its type and attributes
- ✗Thinking it works at compile time, when
type/dirresolve dynamically at runtime - ✗Assuming it needs a metaclass — plain objects are fully introspectable
Follow-up questions
- →What is the difference between
dir(obj)andobj.__dict__? - →How does reflection go beyond introspection?
MiddleTheoryOccasionalWhat does each argument of getattr(obj, name, default) do, and how does it differ from obj.name?
What does each argument of getattr(obj, name, default) do, and how does it differ from obj.name?
name is a runtime string, so getattr resolves names obj.name cannot express; if name is missing it returns default instead of raising AttributeError. It runs the same __getattribute__/__getattr__ lookup as obj.name. Siblings: setattr, delattr, hasattr.
Common mistakes
- ✗Thinking
defaultis the success return value rather than the missing-name fallback - ✗Assuming
getattrbypasses__getattribute__/__getattr__and reads__dict__directly - ✗Forgetting
hasattr/delattrexist and reinventing them withtry/exceptor__dict__
Follow-up questions
- →How does
getattrinteract with__getattr__and__getattribute__? - →When would you prefer
getattr(obj, name, default)over atry/except AttributeErrorblock?
MiddleTheoryOccasionalHow does a metaclass intercept and customize class creation?
How does a metaclass intercept and customize class creation?
When a class body finishes, Python calls the metaclass's __new__/__init__ with the name, bases, and namespace. It can inspect or modify the namespace, alter bases, register the class, then return the built class — once, at definition time.
Common mistakes
- ✗Confusing class-creation time (once) with per-instance creation on every call
- ✗Assuming the namespace is read-only when the metaclass can freely mutate it
- ✗Forgetting the metaclass receives name, bases, and namespace as its inputs
Follow-up questions
- →What is the difference between a metaclass
__new__and__init__? - →When does
__init_subclass__run compared to a metaclass hook?
MiddleTheoryOccasionalHow does reflection differ from introspection in Python?
How does reflection differ from introspection in Python?
Introspection only examines an object at runtime; reflection goes further and manipulates it — getting and setting attributes via getattr/setattr/delattr, calling methods, or creating objects by name, unknown at compile time.
Common mistakes
- ✗Treating reflection and introspection as synonyms — reflection also mutates and acts
- ✗Believing reflection is read-only — it sets and deletes attributes too
- ✗Assuming attribute names must be literal in source rather than runtime strings
Follow-up questions
- →How do you call a method whose name is only known at runtime?
- →What risks come with dynamically setting attributes via
setattr?
MiddleTheoryOccasionalHow does type act as both a function and the default metaclass?
How does type act as both a function and the default metaclass?
Called with one argument, type(obj) returns the object's type. Called with three, type(name, bases, namespace) dynamically creates a new class — exactly what the class statement does under the hood via the default metaclass type.
Common mistakes
- ✗Thinking three-arg
type(...)makes an instance rather than a brand-new class - ✗Believing
typecan only inspect and never construct class objects - ✗Forgetting that the
classstatement itself dispatches totypeby default
Follow-up questions
- →What are the three arguments to
typewhen creating a class? - →How would you subclass
typeto write your own metaclass?
SeniorTheoryRareWhen are metaclasses actually used in practice?
When are metaclasses actually used in practice?
Mostly to build declarative framework APIs: Django's models.Model metaclass turns class-level field declarations into DB descriptors and registers the model. Use them for registration, validation, or adding methods when a decorator is not enough.
Common mistakes
- ✗Reaching for a metaclass where a decorator or
__init_subclass__is simpler - ✗Believing metaclasses are needed for ordinary inheritance to work at all
- ✗Thinking their purpose is performance rather than declarative class building
Follow-up questions
- →How does
__init_subclass__cover many cases that once needed a metaclass? - →Why can combining incompatible metaclasses cause a metaclass conflict?