Python types.new_class: Dynamic Classes

Published on: August 30, 2026
Reading time: 3 minutes
A person typing on a laptop with a Python programming book visible, capturing technology and learning.

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.

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.

External sources

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python code execution and performance monitoring
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: Low-Overhead Instrumentation

    Learn Python sys.monitoring for low-overhead instrumentation with selective events, callbacks, tooling, and safe observability.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Software developer organizing object data with Python operator.attrgetter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.attrgetter: Sort Objects by Attributes

    Learn Python operator.attrgetter to sort, group, and transform objects by simple or nested attributes with clearer reusable code.

    Ler mais

    Tempo de leitura: 4 minutos
    02/09/2026
    Asynchronous programming with Python asyncio.Runner
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Runner: Reuse the Event Loop Safely

    Learn Python asyncio.Runner to reuse an event loop, control context, signals, debug mode, cancellation, and safe asynchronous shutdown.

    Ler mais

    Tempo de leitura: 6 minutos
    02/09/2026
    Binary data compression with Zstandard in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Zstandard Streams and Dictionaries

    Learn Python compression.zstd for Zstandard compression, streaming, dictionaries, safe limits, testing, and production workflows.

    Ler mais

    Tempo de leitura: 6 minutos
    01/09/2026
    Python application packaged as an executable zipapp archive
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipapp: Build Executable Apps

    Learn Python zipapp to package applications as executable pyz archives, include dependencies, and distribute tools safely.

    Ler mais

    Tempo de leitura: 5 minutos
    01/09/2026
    Python code used to compose functions with functools.Placeholder
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: Positional Gaps in partial

    Learn Python functools.Placeholder to leave positional gaps in partial functions and build clearer reusable functional APIs.

    Ler mais

    Tempo de leitura: 5 minutos
    31/08/2026