Skip to content

type() for Dynamic Class Creation#

type is not only the metaclass of every class — it is also a callable factory that builds classes at runtime from a name, base tuple, and namespace dictionary. Framework authors, plugin systems, and code generators use it when class shape depends on data only known at runtime.

How to use this page

Read with Metaclassesclass Foo: pass ultimately calls type. For safer structured records, compare with @dataclass in Classes and OOP Basics. For executing generated code strings, see Code Generations using exec() and eval().

At a glance
Track Python Advanced → Meta Programming
Sections 10 major topics
Outline Use the right-hand TOC to jump

Topics: Two faces of type · Minimal dynamic class · Inheritance at runtime · Building classes from configuration · type() with a custom metaclass · types.new_class — the precise tool · Methods and the namespace dict · __slots__ and memory-conscious dynamic classes · … (+2 more)

  1. Two faces of type
  2. Minimal dynamic class
  3. Inheritance at runtime
  4. Building classes from configuration
  5. type() with a custom metaclass
  6. types.new_class — the precise tool
  7. Methods and the namespace dict
  8. __slots__ and memory-conscious dynamic classes
  9. Security and trust boundaries
  10. Comparison table

Two faces of type#

class Dog:
    pass

type(Dog)   # <class 'type'>  — query: what is Dog's class?
type("Cat", (), {})  # <class 'Cat'>  — factory: build a new class
Call form Purpose
type(obj) Return the class of obj
type(name, bases, namespace) Create a new class dynamically

Minimal dynamic class#

def greet(self) -> str:
    return f"Hello, {self.name}!"

Person = type("Person", (), {"greet": greet})

p = Person()
p.name = "Ada"
p.greet()  # 'Hello, Ada!'

Equivalent static form:

class Person:
    def greet(self) -> str:
        return f"Hello, {self.name}!"

The namespace dict can include any attribute that a class body could define: methods, class variables, __slots__, descriptors, docstrings.

Animal = type(
    "Animal",
    (),
    {
        "__doc__": "A dynamically created animal.",
        "kingdom": "Animalia",
        "__repr__": lambda self: f"<Animal {self.name!r}>",
    },
)

Inheritance at runtime#

class Base:
    def ping(self) -> str:
        return "pong"

def shout(self) -> str:
    return self.ping().upper()

Loud = type("Loud", (Base,), {"shout": shout})

Loud().shout()  # 'PONG'
issubclass(Loud, Base)  # True

Multiple inheritance works like static classes:

Mixin = type("Mixin", (), {"tag": "mixed"})
Combo = type("Combo", (Base, Mixin), {})
Combo.__mro__
# (<class 'Combo'>, <class 'Base'>, <class 'Mixin'>, <class 'object'>)

MRO details: Method Resolution Order.


Building classes from configuration#

A common pattern: JSON/YAML/schema → class definition.

from typing import Any

def model_from_fields(name: str, fields: dict[str, type]) -> type:
    def __init__(self, **kwargs: Any) -> None:
        for key, expected_type in fields.items():
            if key not in kwargs:
                raise TypeError(f"missing required field: {key}")
            value = kwargs[key]
            if not isinstance(value, expected_type):
                raise TypeError(
                    f"{key} expected {expected_type.__name__}, "
                    f"got {type(value).__name__}"
                )
            setattr(self, key, value)

    def __repr__(self) -> str:
        parts = ", ".join(f"{k}={getattr(self, k)!r}" for k in fields)
        return f"{name}({parts})"

    namespace = {"__init__": __init__, "__repr__": __repr__}
    return type(name, (), namespace)


User = model_from_fields("User", {"id": int, "email": str})
u = User(id=1, email="ada@example.com")
repr(u)  # "User(id=1, email='ada@example.com')"

Production alternative

For field-driven classes, prefer dataclasses.make_dataclass or Pydantic models — better validation, typing, and IDE support than hand-rolled type().

from dataclasses import make_dataclass

User = make_dataclass("User", [("id", int), ("email", str)])

type() with a custom metaclass#

class VerboseMeta(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        print(f"Building {name}")
        return super().__new__(mcs, name, bases, namespace)


Dynamic = type(
    "Dynamic",
    (),
    {"x": 1, "__metaclass__": VerboseMeta},  # WRONG — see below
)

Interview trap — __metaclass__ in namespace is ignored

Python 3 does not read __metaclass__ from the namespace dict. Pass metaclass= via a metaclass-aware helper or use types.new_class:

import types

Dynamic = types.new_class(
    "Dynamic",
    (),
    {"metaclass": VerboseMeta},
    lambda ns: ns.update({"x": 1}),
)

See Metaclasses for the full metaclass protocol.


types.new_class — the precise tool#

types.new_class mirrors the class statement, including metaclass, __prepare__, and execution of a body function:

import types

def body(ns: dict) -> None:
    ns["value"] = 42
    def double(self) -> int:
        return self.value * 2
    ns["double"] = double

Widget = types.new_class("Widget", (), {}, body)
Widget().double()  # 84
Tool When to use
type(name, bases, ns) Quick dynamic class, full namespace already built
types.new_class(...) Need metaclass, __prepare__, or body-function semantics
class statement Static, readable, import-time definition

Methods and the namespace dict#

Functions placed in the namespace become descriptors (function objects) that bind to instances on access — same as def inside a class body:

def __init__(self, value: int) -> None:
    self.value = value

def increment(self, step: int = 1) -> int:
    self.value += step
    return self.value

Counter = type("Counter", (), {"__init__": __init__, "increment": increment})

Counter().increment(5)

For closures over per-class state, attach class variables in the namespace:

def make_counter() -> type:
    def __init__(self) -> None:
        self.count = 0

    def tick(self) -> int:
        self.count += 1
        return self.count

    return type("Counter", (), {"__init__": __init__, "tick": tick})

__slots__ and memory-conscious dynamic classes#

Point = type("Point", (), {"__slots__": ("x", "y")})

p = Point()
p.x = 1  # OK
# p.z = 3  # AttributeError — no __dict__

Dynamic classes support the same optimization knobs as static ones.


Security and trust boundaries#

Never feed untrusted input into type()

If the name, base classes, or namespace contents come from user input, an attacker can inject arbitrary methods or inherit dangerous mixins. Treat dynamic class creation like exec() / eval() — only trusted, validated schemas.

Risk Mitigation
Arbitrary code in namespace Build namespace yourself; never exec into it from users
Evil base classes Whitelist allowed bases
Class name injection Sanitize name to valid identifiers

Comparison table#

Feature class statement type(...) make_dataclass
Readable Excellent Poor Good
Runtime fields No Yes Yes
Metaclass support Native Via types.new_class Limited
Validation Manual Manual Basic
IDE / typing Best Weak Good

Interview traps (quick reference)#

Trap What goes wrong Safe approach
type("C", (), {"__metaclass__": M}) Metaclass ignored types.new_class(..., metaclass=M)
Expecting type and class to differ They use the same protocol Understand sugar vs factory
User-controlled namespace Code execution Whitelist fields, no raw exec
Forgetting __init__ in namespace Instances lack initializer Include or inherit __init__
Dynamic class per request in web app Memory leak / metaclass churn Cache by schema hash

Mental model checklist#

  1. What three arguments does type(name, bases, ns) accept?
  2. How is type("X", (), {"f": f}) equivalent to a class statement?
  3. Why doesn't __metaclass__ in the namespace dict work in Python 3?
  4. When is types.new_class required over bare type()?
  5. What are safer alternatives for schema-driven classes?

What's next#

Topic Page
Metaclass protocol Metaclasses
Class decorators Class Decorators
exec / eval risks Code Generations using exec() and eval()
OOP foundations Classes and OOP Basics
Introspection Inspect Module for Introspection