We know that along with the standard class statement, Python also allows us to create classes dynamically by calling type() (or another metaclass if our class has a metaclass other than type)
I already dedicated a rather thick post to type. Basically we use it for creating a new class like this: type(classname, superclasses, namespace). (the namespace is just a dictionary with the attributes).
So I was wondering if the compiler translates a class statement into a call to type(), and yes, more or less we can say so, but there are some extras. I've had a really insightful conversation with a GPT about this, and additionally I've found an excellent article that explains it in full detail
The steps that Python follows when it comes across with a class statement (class Foo(Base, metaclass=Meta): x = 1) are these (I'm taking it from a GPT discussion, it's basically the same that is explained in the linked article)
- Step 1 — Determine the metaclass. Python calls __build_class__ (a builtin), which inspects the bases and the explicit metaclass= kwarg to resolve which metaclass to use (with MRO-based metaclass conflict resolution).
- Step 2 — Prepare the namespace. The metaclass's __prepare__ classmethod is called: namespace = Meta.__prepare__('Foo', (Base,), **kwargs). This returns the dict (or dict-like object) that will serve as the class namespace. For type, this is just a plain dict. For enum.EnumMeta, for example, it returns a special _EnumDict.
- Step 3 — Execute the class body. The compiled code object for the class body is executed as a function, with the namespace from step 2 as its locals(). This is the key insight: it's essentially exec(body_code, globals(), namespace). After this, namespace contains {'x': 1, '__module__': ..., '__qualname__': ...}.
- Step 4 — Call the metaclass. Meta('Foo', (Base,), namespace) is called — which, for the default type, invokes type.__call__ → type.__new__ → type.__init__. This is where the actual class object is constructed.
How do the above steps look at the bytecode level? When the Python compiler (yes, in Python, where compilation is like a hidden step that happens the first time our code is run (or has changed), it's sometimes confusing to establish the difference between compilation time and execution time), comes across a class statement, it creates a code object for the code that we've placed inside that statement (the body of the class statement), along with code objects for each function (method) defined in that code, and creates a sequence of bytecode instructions that at runtime will make use of that code object (and many more things) to create a class object (yes, remember that classes are objects).
That sequence of bytecode instructions can vary slightly with Python versions (what I'll show below, that corresponds to python 3.14 is slightly different from what is shown in the aforementioned article), but the intent is the same.
class Person:
pass
# translates into:
0 RESUME 0
1 LOAD_BUILD_CLASS
PUSH_NULL
LOAD_CONST 0 (code object Person at 0x78d07576e730, file "class_creation.py", line 1)
MAKE_FUNCTION
LOAD_CONST 1 ('Person')
CALL 2
STORE_NAME 0 (Person)
LOAD_CONST 2 (None)
RETURN_VALUE
So those seem like very few instructions for the complex 4 steps that I've just described!. Well, that's because all the magic happens in a builtin function __build_class, that is loaded by the LOAD_BUILD_CLASS bytecode instruction. The article makes a great job explaining these opcodes.
When we create a class dynamically using type() (or any other metaclass), we are directly at step 4, we are skipping the first 3 steps. Obviously it's us who choose the metaclass to use, and there's not class body to execute. And as we create ourselves the namespace object to pass to the metaclass the __prepare__ method that helps prepare that namespace is not executed. That's maybe the main difference then, that in the dynamic class creation the metaclass __prepare__ method does not intervene. That's interesting, cause indeed I was not familiar with that __prepare__ method (also referred as hook).
When talking about metaclasses I always think about __new__ and __init__ (and __call__ that intervenes when instances of a class created by the metaclass are created), I've talked about them in different posts, one of the most interesting being this, but was unfamiliar with __prepare__. We've seen that it allows us to prepare the namespace, OK, but when can we need that? Well, very rarely (this is particularly dark metaclass stuff). That can be material for another post, for now I'll just say that enum.EnumMeta makes use of it.