Class creation in Python is the process that turns a class statement into a fully initialized class object. When Python encounters the class keyword, it does not simply store the body as a static definition. It executes code, builds a namespace, determines the metaclass, and calls multiple hooks before the class is ready to use.
The article on Python metaclasses covered how metaclasses control class creation from the outside. This article walks through the full creation lifecycle from the inside, showing each step in order and where you can hook into the process.
Understanding class creation helps you debug metaclass issues, implement class registries, and write frameworks that need to interact with class definitions at import time.
The step-by-step creation process
When Python processes a class statement, it follows a precise sequence. First, it extracts the class name, base classes, and keyword arguments from the statement header.
Next, it executes the class body in a new temporary namespace. Every assignment, function definition, and decorator call runs during this execution. The resulting namespace dictionary contains all the names defined in the body.
Python then determines the metaclass. If a metaclass keyword is present, that value is used; otherwise, Python finds the most derived metaclass among the base classes. If no base class has a metaclass, type is the default.
The metaclass new method is called with the class name, base tuple, and namespace dictionary. This method returns the new class object. Then the metaclass init method initializes the newly created class.
Finally, the init subclass method is called on the new class. This hook lets parent classes customize their subclasses after creation without requiring a metaclass.
Hooking into subclass creation with init subclass
The init subclass hook, available since Python 3.6, is the simplest way to react to new subclasses. Define it on a base class and Python calls it automatically whenever a subclass of that base is created.
class Base:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Base.subclasses.append(cls)
class User(Base):
pass
class Product(Base):
pass
for cls in Base.subclasses:
print(cls.__name__)Each subclass of Base is automatically registered in the subclasses list. No metaclass is required, and the subclasses themselves need no special syntax or decorators.
User
ProductThe init subclass method receives keyword arguments passed in the class definition header. This lets parent classes expose configuration options to their subclasses.
class Tagged:
def __init_subclass__(cls, tag=None, **kwargs):
super().__init_subclass__(**kwargs)
cls.tag = tag
class Widget(Tagged, tag="ui"):
pass
print(Widget.tag)The tag keyword argument is consumed by Tagged init subclass method and stored as a class attribute on Widget. The subclass itself required no special code beyond passing the argument.
uiExecuting the class body as code
The class body is not a passive declaration. It is executed as code, which means you can put arbitrary logic inside it. This is how computed class attributes and conditional definitions work.
import sys
class Config:
DEBUG = True
LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
if sys.platform == "darwin":
PLATFORM = "macos"
elif sys.platform == "linux":
PLATFORM = "linux"
else:
PLATFORM = "other"
print(Config.LOG_LEVEL)
print(Config.PLATFORM)The body executes at definition time with full access to the enclosing scope. Conditional logic and computed values work just as they would in a function body.
DEBUG
macosThis code execution happens before the metaclass creates the class. By the time new runs, the namespace dictionary is fully populated with all the names defined during body execution.
Using metaclass new to validate the namespace
The metaclass new method receives the complete namespace dictionary before the class is created. This is the right place to validate that required methods exist, check naming conventions, or inject additional attributes.
class InterfaceMeta(type):
def __new__(mcs, name, bases, namespace):
if name != "Service":
required = namespace.get("__required__", [])
missing = [m for m in required if m not in namespace]
if missing:
raise TypeError(f"{name}: missing {missing}")
return super().__new__(mcs, name, bases, namespace)
class Service(metaclass=InterfaceMeta):
__required__ = ["start", "stop"]
class WebServer(Service):
def start(self): return "started"
def stop(self): return "stopped"
server = WebServer()
print(server.start())The metaclass checks that every Service subclass defines start and stop. The validation happens at class definition time, failing fast with a clear error message.
startedThe init subclass hook can also validate subclasses, but it runs after the class is created. The metaclass new hook runs before creation, which means the invalid class never comes into existence.
Combining all hooks in order
When you use all three hooks together, the execution order is predictable. The class body executes first, populating the namespace.
The metaclass new creates the class object. The metaclass init initializes it. The init subclass hook fires last.
This order lets you split responsibilities across hooks. Use the class body for attribute definition, metaclass new for validation and transformation before creation, metaclass init for post-creation setup that needs the fully formed class, and init subclass for parent-class reactions to new subclasses.
The article on Python namespace and scope internals covers how the namespace dictionary built during class creation relates to the broader namespace system Python uses everywhere else.
Rune AI
Key Insights
- The class body is executed in a temporary namespace; all names defined become class attributes.
- The metaclass is determined from the metaclass keyword, base class types, or defaults to type.
- metaclass.new creates the class object; metaclass.init initializes it after creation.
- init_subclass is called on the new class, giving parent classes a hook for subclass customization.
- Use init_subclass for simple subclass registration; use metaclasses only when you need to control creation itself.
Frequently Asked Questions
What happens when Python executes a class statement?
How is the metaclass for a class determined?
Conclusion
Class creation in Python is a multi-step process that you can hook into at several points. The namespace execution lets you compute class attributes dynamically. The metaclass new and init methods let you control and validate the class object itself. The init subclass hook lets parent classes react to new subclasses. Each hook serves a different purpose, and knowing all three gives you complete control over how your classes come into existence.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.