Zum Hauptinhalt springen

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:

  1. class name;
  2. base-class tuple;
  3. 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:

  1. call User.__new__;
  2. if appropriate, call User.__init__;
  3. 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

TermMeaning
metaclassclass responsible for creating classes
class objectruntime object produced by a class statement
namespace preparationcreation of class-body mapping
subclass hookbehavior triggered when a subclass is defined
registrymapping from identifiers to implementations
instance allocationcreation of a new object before initialization
metaclass conflictincompatible metaclasses in base classes
import-time side effectbehavior occurring during module import
declarative APIconfiguration expressed through definitions
dynamic injectionadding 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:

  1. class-body execution;
  2. classes as instances of type;
  3. dynamic class creation;
  4. instance __new__;
  5. immutable subclass creation;
  6. class decorators;
  7. __init_subclass__;
  8. metaclass __new__, __init__, and __call__;
  9. metaclass conflicts;
  10. import-time registration;
  11. choosing the least powerful mechanism.