Chapter 16 — Class Creation, __new__, __init_subclass__, and Metaclasses
1. Opening problem
This class definition is executable:
class User:
print("Creating User class")
role = "member"
The message is printed when the class statement runs, usually during import.
Python then creates a class object and binds the name User to it.
Classes are runtime objects. Their creation can be customized through:
__new__;- class decorators;
__init_subclass__;type;- metaclasses;
- namespace preparation.
These features power frameworks, ORMs, plugin registries, declarative APIs, and validation systems.
They are also easy to overuse.
2. Class-body execution
prefix = "user"
class User:
table_name = (
prefix + "_accounts"
)
The class body executes in its own namespace.
Names created there become class attributes.
Functions defined there become function objects that later act as descriptors.
The class body can contain arbitrary statements:
class Example:
for index in range(3):
locals()[
f"value_{index}"
] = index
This is legal but usually harms readability.
Declarative class syntax should remain understandable.
3. Classes are instances of metaclasses
class User:
pass
print(type(User))
Typically:
<class 'type'>
type is the default metaclass.
Instances relate to classes:
user instance → instance of User
User class → instance of type
This is why classes can be called, inspected, decorated, and stored like other objects.
4. Dynamic class creation with type
Three-argument type:
User = type(
"User",
(object,),
{
"role": "member",
},
)
Equivalent in spirit to:
class User:
role = "member"
Arguments:
- class name;
- base-class tuple;
- namespace dictionary.
Dynamic class creation is useful in some framework and code-generation scenarios.
Do not use it when a normal class statement is clearer.
5. Instance __new__
__new__ creates or returns an instance.
class User:
def __new__(
cls,
*args,
**kwargs,
):
instance = super().__new__(
cls
)
return instance
def __init__(
self,
name: str,
) -> None:
self.name = name
Ordinary mutable classes rarely need custom __new__.
Use cases include:
- immutable subclasses;
- instance caching;
- singleton-like behavior;
- returning a different class;
- low-level allocation control.
6. Immutable type subclassing
class NormalizedString(str):
def __new__(
cls,
value: str,
):
normalized = (
value.strip().lower()
)
return super().__new__(
cls,
normalized,
)
Why not __init__?
A string's value is fixed when the object is created. Normalization must occur in __new__.
Usage:
value = NormalizedString(
" Hello "
)
print(value)
7. Instance caching
class Symbol:
_instances: dict[
str,
"Symbol",
] = {}
def __new__(
cls,
value: str,
):
normalized = value.strip()
if normalized in cls._instances:
return cls._instances[
normalized
]
instance = super().__new__(
cls
)
cls._instances[
normalized
] = instance
return instance
Danger:
__init__ may run again even when __new__ returns an existing instance.
You must design initialization carefully.
Caching instances also raises concerns:
- memory growth;
- thread safety;
- lifecycle;
- subclass behavior;
- test isolation;
- hidden identity semantics.
A factory or cache object is often clearer.
8. Class decorators
CLASS_REGISTRY: dict[
str,
type,
] = {}
def register(
name: str,
):
def decorator(cls):
if name in CLASS_REGISTRY:
raise ValueError(
f"Duplicate class: {name}"
)
CLASS_REGISTRY[
name
] = cls
return cls
return decorator
Usage:
@register("user-created")
class UserCreatedHandler:
pass
A class decorator receives the completed class object and returns a class object.
This is often simpler than a metaclass.
9. __init_subclass__
A base class can receive notifications when subclasses are created.
class Handler:
registry: dict[
str,
type["Handler"],
] = {}
def __init_subclass__(
cls,
*,
event_type: str,
**kwargs,
) -> None:
super().__init_subclass__(
**kwargs
)
if event_type in cls.registry:
raise ValueError(
f"Duplicate event type: "
f"{event_type}"
)
cls.registry[
event_type
] = cls
Usage:
class UserCreatedHandler(
Handler,
event_type="user-created",
):
pass
This provides declarative registration without a metaclass.
10. Validating subclasses
class Plugin:
def __init_subclass__(
cls,
**kwargs,
) -> None:
super().__init_subclass__(
**kwargs
)
if not hasattr(
cls,
"plugin_name",
):
raise TypeError(
"plugin_name is required"
)
This can enforce class-level conventions.
However, static protocols or abstract methods may provide clearer contracts.
Use __init_subclass__ for class-creation behavior, not ordinary instance validation.
11. Cooperative __init_subclass__
Multiple base classes may define subclass hooks.
Each should call:
super().__init_subclass__(
**kwargs
)
and consume only its own keyword options.
This mirrors cooperative constructor design.
Failure to forward can break other base classes.
12. Metaclasses
A metaclass creates classes.
class Meta(type):
def __new__(
metaclass,
name,
bases,
namespace,
**kwargs,
):
print(
f"Creating class {name}"
)
return super().__new__(
metaclass,
name,
bases,
namespace,
)
Usage:
class Example(
metaclass=Meta
):
pass
Metaclass __new__ receives:
- metaclass;
- class name;
- base classes;
- class namespace;
- optional class-definition keywords.
It returns the class object.
13. Metaclass __init__
class Meta(type):
def __init__(
cls,
name,
bases,
namespace,
**kwargs,
) -> None:
super().__init__(
name,
bases,
namespace,
)
Difference:
- metaclass
__new__creates the class object; - metaclass
__init__initializes the created class object.
As with instance creation, most customization can often happen in __new__.
14. Metaclass __call__
Calling a class invokes its metaclass's __call__.
Conceptually:
instance = User(...)
goes through:
type(User).__call__(
User,
...
)
Default metaclass behavior:
- call
User.__new__; - if appropriate, call
User.__init__; - return the instance.
A metaclass can intercept instance creation, but this is extremely powerful and often too implicit.
15. Metaclass conflict
Suppose two base classes use incompatible metaclasses:
class MetaA(type):
pass
class MetaB(type):
pass
class A(
metaclass=MetaA
):
pass
class B(
metaclass=MetaB
):
pass
Attempt:
class C(A, B):
pass
may raise a metaclass conflict.
The derived class's metaclass must be compatible with the metaclasses of all bases.
This is one reason framework inheritance can become difficult.
Composition and protocols avoid many metaclass conflicts.
16. __prepare__
A metaclass can customize the namespace used during class-body execution.
class Meta(type):
@classmethod
def __prepare__(
metaclass,
name,
bases,
**kwargs,
):
return {}
Historically, this supported ordered namespaces before normal dictionaries preserved insertion order.
Modern uses include:
- tracking definition order;
- rejecting duplicate names;
- custom declarative namespaces.
This is advanced framework machinery. Most application code should not need it.
17. Automatic field collection
A metaclass can inspect descriptors:
class ModelMeta(type):
def __new__(
metaclass,
name,
bases,
namespace,
):
fields = {
key: value
for key, value
in namespace.items()
if isinstance(
value,
Field,
)
}
cls = super().__new__(
metaclass,
name,
bases,
namespace,
)
cls.__fields__ = fields
return cls
Usage:
class User(
metaclass=ModelMeta
):
email = Field()
name = Field()
This resembles ORM and validation frameworks.
Before building such a system, consider whether:
- a class decorator;
__init_subclass__;- dataclasses;
- explicit registration;
- normal composition
would be simpler.
18. Class decorators versus __init_subclass__ versus metaclasses
Class decorator
Best when:
- transformation is opt-in;
- behavior applies to selected classes;
- no control over subclass creation is needed;
- the class already exists.
__init_subclass__
Best when:
- a base class owns subclass registration or validation;
- behavior should apply automatically to subclasses;
- metaclass control is unnecessary.
Metaclass
Best when:
- class creation itself must be deeply controlled;
- multiple classes share framework-level creation semantics;
- namespace preparation is needed;
- class instantiation behavior must be customized.
Choose the least powerful mechanism that solves the problem.
19. Registry case study
Simplest explicit registry:
HANDLERS = {
"user-created": (
UserCreatedHandler
),
}
Class decorator:
@register("user-created")
class UserCreatedHandler:
...
Subclass hook:
class UserCreatedHandler(
Handler,
event_type="user-created",
):
...
Metaclass:
class UserCreatedHandler(
Handler,
metaclass=HandlerMeta,
):
event_type = "user-created"
The explicit dictionary is easiest to understand.
Add machinery only when the number of classes, plugin discovery, or framework constraints justify it.
20. Import-time behavior
Registries built through class creation depend on modules being imported.
A handler that is never imported is never registered.
This can cause confusing production failures.
Possible solutions:
- explicit imports;
- plugin entry points;
- startup discovery;
- generated registry;
- explicit configuration.
Do not assume class definitions execute automatically merely because files exist.
21. Type annotations and metaclasses
Metaclass-heavy APIs can challenge static type checkers.
Dynamic field injection may be invisible to tooling.
For example:
class User(
metaclass=ModelMeta
):
email = Field()
A framework may dynamically add constructor parameters and attributes.
Static analysis may require:
- plugins;
- generated stubs;
- protocols;
- explicit annotations;
- dataclass transforms;
- framework-specific support.
Runtime cleverness often increases typing complexity.
22. dataclass_transform
Advanced libraries can use typing metadata to tell type checkers that a decorator or metaclass behaves like a dataclass.
This is useful for framework authors.
Application developers should generally prefer established libraries rather than implementing their own type-checker integration.
23. TypeScript comparison
TypeScript classes exist at runtime, but interfaces do not.
Decorators and static blocks can influence class behavior.
Python class bodies execute directly, and metaclasses customize class creation itself.
Python's metaclass model is more general but also more complex.
A TypeScript developer should resist using metaclasses as an equivalent of decorators or dependency-injection metadata unless class-creation control is genuinely required.
24. Common mistakes
Metaclass for simple registration
Use a dictionary or class decorator.
Hidden import dependency
Registration requires module execution.
Singleton through __new__
A factory or dependency container is usually clearer.
Dynamic field injection
Static tooling and readability suffer.
Forgetting cooperative subclass hooks
Forward with super().
Metaclass conflict
Framework base classes may be incompatible.
Class-body side effects
Avoid network, database, or environment-dependent work during import.
Excessive magic
Prefer explicit architecture.
25. English vocabulary
| Term | Meaning |
|---|---|
| metaclass | class responsible for creating classes |
| class object | runtime object produced by a class statement |
| namespace preparation | creation of class-body mapping |
| subclass hook | behavior triggered when a subclass is defined |
| registry | mapping from identifiers to implementations |
| instance allocation | creation of a new object before initialization |
| metaclass conflict | incompatible metaclasses in base classes |
| import-time side effect | behavior occurring during module import |
| declarative API | configuration expressed through definitions |
| dynamic injection | adding behavior or attributes at runtime |
Useful sentences:
- “The class body executes during module import.”
- “
__new__creates the immutable instance before initialization.” - “A class decorator is sufficient for this registry.”
- “The subclass hook must cooperate through
super().” - “The metaclass introduces a static-analysis burden.”
- “Registration depends on the module being imported.”
26. Speaking task
Explain for ten minutes:
What happens from the moment Python reads a class statement until an instance is created?
Include class-body execution, metaclass creation, class call, __new__, and __init__.
27. Writing task
Write a 450-word design review of a custom ORM that uses a metaclass for validation, singleton management, database connections, route registration, and automatic API serialization.
28. Exercises
Exercise 1
Create a normalized immutable string subclass using __new__.
Exercise 2
Create a class decorator that registers command classes and rejects duplicates.
Exercise 3
Create a base plugin class using __init_subclass__ and a required plugin name.
Exercise 4
Implement a metaclass that rejects public methods without docstrings.
Exercise 5
Compare four registry implementations and choose the simplest appropriate one.
29. Complete solutions
Solution 1
class NormalizedString(str):
def __new__(
cls,
value: str,
):
if not isinstance(
value,
str,
):
raise TypeError(
"value must be a string"
)
normalized = (
value.strip().lower()
)
if not normalized:
raise ValueError(
"value must not be blank"
)
return super().__new__(
cls,
normalized,
)
Solution 2
COMMAND_CLASSES: dict[
str,
type,
] = {}
def command(
name: str,
):
def decorator(cls):
if name in COMMAND_CLASSES:
raise ValueError(
f"Duplicate command: {name}"
)
COMMAND_CLASSES[
name
] = cls
return cls
return decorator
Solution 3
class Plugin:
registry: dict[
str,
type["Plugin"],
] = {}
def __init_subclass__(
cls,
*,
plugin_name: str,
**kwargs,
) -> None:
super().__init_subclass__(
**kwargs
)
normalized = (
plugin_name
.strip()
.lower()
)
if not normalized:
raise ValueError(
"plugin_name must not be blank"
)
if normalized in cls.registry:
raise ValueError(
f"Duplicate plugin: "
f"{normalized}"
)
cls.registry[
normalized
] = cls
Solution 4
class DocumentedMethodsMeta(
type
):
def __new__(
metaclass,
name,
bases,
namespace,
):
for attribute_name, value in (
namespace.items()
):
if (
attribute_name.startswith(
"_"
)
):
continue
if callable(value):
if not getattr(
value,
"__doc__",
None,
):
raise TypeError(
f"Public method "
f"{attribute_name!r} "
"requires a docstring"
)
return super().__new__(
metaclass,
name,
bases,
namespace,
)
This is educational. A linter is usually a better solution.
Solution 5
Use an explicit dictionary when implementations are known and stable.
Use a class decorator for opt-in registration without inheritance.
Use __init_subclass__ when every subclass of a framework base must register.
Use a metaclass only when class creation itself requires deeper control.
30. Chapter checkpoint
You should now be able to explain:
- class-body execution;
- classes as instances of
type; - dynamic class creation;
- instance
__new__; - immutable subclass creation;
- class decorators;
__init_subclass__;- metaclass
__new__,__init__, and__call__; - metaclass conflicts;
- import-time registration;
- choosing the least powerful mechanism.