types.new_class() creates classes dynamically while following the core rules of a normal class statement. It selects the appropriate metaclass, prepares the class namespace, and runs a callback that inserts attributes, methods, and metadata before the final type is built.
It is useful in frameworks, ORMs, model generators, plugin systems, and schema-driven APIs. For simple cases, type() may be enough; new_class is valuable when metaclasses, __prepare__, or dynamic bases matter.
Signature
types.new_class(name, bases=(), kwds=None, exec_body=None)
name sets the class name, bases contains base classes, kwds represents class-header options, and exec_body receives the prepared namespace.
Basic example
from types import new_class
def populate(namespace):
namespace["category"] = "dynamic"
def describe(self):
return self.category
namespace["describe"] = describe
Product = new_class("Product", (), {}, populate)
print(Product().describe())
The callback modifies the namespace before class creation.
Conceptual equivalent
class Product:
category = "dynamic"
def describe(self):
return self.category
A normal declaration is preferable when the structure is known while writing the program. Dynamic generation should be reserved for definitions determined at runtime.
Using a base class
class Model:
def save(self):
print("saving", type(self).__name__)
def body(ns):
ns["table"] = "customers"
Customer = new_class("Customer", (Model,), exec_body=body)
The generated class participates normally in inheritance, MRO, descriptors, and super().
Explicit metaclass
class Meta(type):
def __new__(mcls, name, bases, namespace):
namespace["registered"] = True
return super().__new__(mcls, name, bases, namespace)
Generated = new_class(
"Generated",
(),
{"metaclass": Meta},
lambda ns: ns.update(value=10),
)
The kwds dictionary corresponds to class-header keywords. The metaclass option participates in class creation rather than becoming an ordinary class attribute.
__prepare__ and custom namespaces
Metaclasses can implement __prepare__ to return a specialized mapping. new_class respects this protocol, unlike a simplistic approach that builds a dictionary and calls type.
types.prepare_class
For complete control, types.prepare_class() calculates the metaclass and returns the prepared namespace. new_class combines those steps into a convenient interface.
Dynamic bases and __mro_entries__
Objects used as bases can provide __mro_entries__ and be replaced by actual classes. The types module also provides resolve_bases. This mechanism appears in generics and advanced frameworks.
Creating methods with closures
def create_model(name, fields):
def body(ns):
ns["__annotations__"] = dict(fields)
def __repr__(self):
values = ", ".join(
f"{field}={getattr(self, field, None)!r}"
for field in fields
)
return f"{name}({values})"
ns["__repr__"] = __repr__
return new_class(name, (), exec_body=body)
Be careful with loop-variable capture when generating multiple methods.
Setting __module__ and __qualname__
Public generated classes should have coherent metadata for documentation, error messages, and serialization.
def body(ns):
ns["__module__"] = __name__
ns["__doc__"] = "Dynamically generated model."
Pickle and importability
Pickle usually needs the class to be available through an importable module name. A class created inside a function may need to be registered in the module namespace.
Applying decorators
The returned class can be passed to dataclasses.dataclass, registries, or other decorators. Frameworks that inspect the class during creation may require attributes to exist inside exec_body.
Security
Do not turn untrusted schemas directly into bases, metaclasses, identifiers, or executable code. Validate names, use allowlists, and avoid exec when structured namespace construction is sufficient.
new_class versus type
type(name, bases, namespace) is concise when the namespace is already available. new_class is better when reproducing the class-definition process, including metaclass selection and prepared namespaces.
Common mistakes
- Generating a class when a dataclass or simple object is enough.
- Forgetting
__module__and breaking pickle or documentation. - Capturing loop variables incorrectly.
- Trusting externally supplied bases or metaclasses.
- Skipping MRO, inheritance, and introspection tests.
Recommended practice
Centralize generation in a factory, validate schemas, choose deterministic names, preserve metadata, and test generated classes as public APIs. See the internal guides to the types module, object-oriented programming, and dataclasses.
Conclusion
types.new_class provides a structured way to generate runtime classes while respecting metaclasses, prepared namespaces, and inheritance. It is best suited to framework infrastructure driven by dynamic definitions.







