Dynamic Class Creation
Classes in Python are ordinary objects created at runtime by type() — understanding that construction step lets you build classes programmatically instead of only with the class keyword.
Every class statement in Python is really syntax sugar: under the hood, the interpreter builds a namespace dict by executing the class body, then calls type(name, bases, namespace) to actually construct the class object. Because that construction step is just a regular callable, classes can be created entirely at runtime — from data, from a loop, from user input — without ever writing a class statement. This topic covers how and when to do that responsibly; the related Metaclasses topic covers customizing the machinery itself (type subclasses) in depth.
What Is Dynamic Class Creation
Dynamic class creation means constructing a class object programmatically at runtime — using type() (or a metaclass, types.new_class, or occasionally exec) instead of a static class Foo: ... statement written in the source. The resulting object is a completely ordinary class in every respect: you can instantiate it, subclass it, and use isinstance() against it, exactly like any class defined the usual way.
Classes Are Objects Created by type()
type is itself a class — the built-in metaclass that all ordinary classes are instances of. Called with one argument, type(obj) returns an object's class (the everyday use you already know). Called with THREE arguments, type(name, bases, namespace) constructs a brand new class: name is the class's __name__ (a string), bases is a tuple of parent classes, and namespace is a dict that becomes the class's attribute dictionary (methods, class variables).
This Person class is fully equivalent to one written with class Person: containing species = "human" and def greet(self): ... — type() built it directly from a name, base tuple, and namespace dict.
def greet(self) -> str:
return f"Hello, I'm {self.name}"
Person = type(
"Person",
(), # no explicit base classes -> inherits object
{"greet": greet, "species": "human"} # methods and class attributes
)
p = Person()
p.name = "Ada"
print(p.greet()) # Hello, I'm Ada
print(Person.species) # human
print(type(p)) # <class '__main__.Person'>class Statement vs type() Are Equivalent
This equivalence is exact, not approximate: class Foo(Base): x = 1 executes the indented body as a mini-program to build a namespace dict ({"x": 1, ...}), then calls type("Foo", (Base,), namespace) to produce the class — same machinery either way. Understanding this demystifies what a class statement actually does and is the foundation for understanding metaclasses, which work by customizing this construction step (overriding type.__new__/__init__).
class Animal:
sound = "..."
def make_sound(self):
return self.sound
# is exactly equivalent to:
def make_sound(self):
return self.sound
Animal2 = type("Animal2", (), {"sound": "...", "make_sound": make_sound})
print(Animal().make_sound() == Animal2().make_sound()) # True (both '...')Building Classes from Data
Because type()'s namespace argument is just a dict, classes can be constructed from data computed at runtime — looping over a specification, reading a schema, or generating variants of a class programmatically. This is genuinely useful for frameworks that build model/schema classes from configuration (ORMs, serialization libraries) rather than requiring one hand-written class per shape.
A factory function builds a distinct exception subclass for each name/message pair supplied, without writing a separate class statement for each.
def make_error_class(name: str, default_message: str) -> type:
def __init__(self, message: str = default_message):
Exception.__init__(self, message)
return type(name, (Exception,), {"__init__": __init__})
NotFoundError = make_error_class("NotFoundError", "resource not found")
TimeoutError2 = make_error_class("TimeoutError2", "operation timed out")
try:
raise NotFoundError()
except NotFoundError as e:
print(e) # resource not foundDynamic Attributes on Created Classes
Attributes and methods can also be added to a class object AFTER it's been created — a class, like any object, can generally have new attributes set on it (setattr(SomeClass, name, value)), which then become visible on all instances (existing and future) unless shadowed by an instance attribute. This is more invasive than building the namespace up front and should be used sparingly, since it can make code harder to trace.
class Widget:
pass
def resize(self, factor: float) -> None:
self.size = getattr(self, "size", 1) * factor
setattr(Widget, "resize", resize) # add a method after class creation
w = Widget()
w.resize(2.0)
print(w.size) # 2.0types.new_class: A More Correct Alternative
Plain type(name, bases, namespace) works for simple cases but bypasses some of the machinery a real class statement triggers — notably __init_subclass__ hooks and metaclass resolution when bases use a custom metaclass. types.new_class(name, bases, kwds, exec_body) (standard library) replicates the full class-creation protocol more faithfully and is the recommended lower-level tool when dynamic creation needs to interact correctly with inheritance, metaclasses, or __init_subclass__.
import types
def exec_body(namespace):
namespace["greeting"] = "hi"
Greeter = types.new_class("Greeter", (), {}, exec_body)
print(Greeter().greeting) # hiexec() for Building Class Source Dynamically
It's technically possible to build a class by assembling Python source as a string and running it through exec(), but this is rarely appropriate: it's harder to read, harder to debug (errors point into a synthetic string, not a real file/line), and dangerous if any part of the source is influenced by untrusted input (see the dedicated exec/eval topic for the security discussion). type() or types.new_class() should be preferred for essentially all legitimate dynamic-class use cases; reach for exec() only in narrow, well-controlled tooling scenarios (e.g. code generators writing to a file, not classes built at request-time from user data).
Relationship to Metaclasses
A metaclass is simply a class whose instances are themselves classes — typically a subclass of type that overrides __new__/__init__ to customize what happens every time a class using that metaclass is created (validating attributes, auto-registering subclasses, injecting methods). Dynamic class creation via type()/types.new_class() is the underlying mechanism that BOTH the ordinary class statement and custom metaclasses ultimately go through — this topic is about *using* that mechanism directly; see the dedicated Metaclasses topic for *customizing* it.
Security Concerns
type(name, bases, namespace) itself is not inherently dangerous — the arguments are ordinary Python objects (a string, a tuple, a dict), not arbitrary code. The risk appears when any of those pieces are built from untrusted input in ways that let an attacker inject unexpected behavior: e.g. a namespace dict whose method bodies came from eval/exec on untrusted text, or bases chosen from an attacker-controlled class name looked up dynamically, potentially subclassing something sensitive. Building classes from a fixed, trusted set of specifications (config files you control, an internal schema) is fine; building them from arbitrary end-user text is not.
Practical Usage
Dynamic class creation is genuinely used in: ORMs generating model classes from database schema definitions, testing frameworks generating parametrized test-case classes, plugin systems assembling classes from discovered component pieces, and collections.namedtuple-style factory functions (which themselves build a class dynamically and return it). It is comparatively rare in everyday application code — for the vast majority of cases, a plain class statement (possibly combined with class decorators, __init_subclass__, or a metaclass) is clearer and should be preferred.
Prefer Explicit Code When Possible
This topic's central caution: dynamic class creation trades readability and static-analysis-friendliness for flexibility. A class built with a normal class statement is discoverable by IDEs, type checkers, and anyone reading the file top to bottom; a class assembled by type() inside a loop or factory function is not, and its shape can only be understood by tracing the code that builds it at runtime. Use dynamic creation only when the genuine requirement is 'generate a family of classes from data I don't know until runtime' — not as a shortcut around writing a few explicit classes, decorators, or a class hierarchy.
Key Takeaways
Every class is created by calling type(name, bases, namespace); a class statement is syntax sugar over exactly this call.
type(name, bases, namespace) builds a class from a string name, a tuple of base classes, and a dict of attributes/methods.
types.new_class() is the more correct tool when dynamic creation must interact properly with metaclasses or __init_subclass__.
Classes support setattr() after creation just like other objects, letting methods/attributes be added post hoc — use sparingly.
exec()-based class construction from source strings should be avoided in favor of type()/types.new_class() except in narrow tooling scenarios.
Security risk arises from untrusted input shaping a class's namespace or bases, not from type() itself.
Prefer an ordinary class statement whenever the set of classes needed is known at write-time; reserve dynamic creation for genuinely data-driven class families.
NewClass = type(class_name, (), {"x": value}) # no source-string assembly needed
Assembling class source as a string and running it through exec() is harder to debug and, if any piece comes from untrusted input, a security risk; type() builds the same class directly from ordinary Python objects.
class Base(metaclass=SomeMeta):... Derived = type("Derived", (Base,), {}) # bypasses SomeMeta's proper construction path import types Derived = types.new_class("Derived", (Base,), {}) # respects metaclass resolution correctly
Plain type() does not fully replicate the class-creation protocol when metaclasses or __init_subclass__ are involved; types.new_class() does, and should be used whenever bases might carry either.
for name in user_supplied_names: # untrusted, e.g. from a web request globals()[name] = type(name, (AdminPanel,), {}) # attacker picks the base class name & attrs
ALLOWED = {"Report", "Summary"} for name in user_supplied_names: if name not in ALLOWED: raise ValueError(f"unsupported class name: {name}") globals()[name] = type(name, (BaseModel,), {})
Letting untrusted input choose class names, base classes, or namespace contents can let an attacker construct classes with unintended inheritance or behavior; validate against an explicit allowlist before dynamically creating classes from external input.
What are the three arguments to type() when it's used to create a class, and what does each one correspond to in a normal class statement?
type(name, bases, namespace): name is the class's name (a string, corresponds to the identifier after class), bases is a tuple of parent classes (corresponds to the parenthesized inheritance list), and namespace is a dict of attributes/methods (corresponds to the indented class body).
Is class Foo: x = 1 doing something fundamentally different from Foo = type('Foo', (), {'x': 1}), or are they equivalent?
They are equivalent — a class statement is syntax sugar: Python executes the class body to build a namespace dict, then calls type(name, bases, namespace) with it, exactly as the manual version does directly.
Why is types.new_class() sometimes preferred over calling type() directly?
types.new_class() more faithfully replicates the full class-creation protocol, correctly handling metaclass resolution among the base classes and triggering __init_subclass__ hooks — details that a bare type(name, bases, namespace) call can skip.
Can attributes/methods be added to a class after it has already been created? How?
Yes — a class is an ordinary object, so setattr(SomeClass, 'method_name', some_function) adds a new method to it, which becomes visible on all its instances (existing and future) unless shadowed. It should be used sparingly since it makes the class's full shape hard to see from the source alone.
Why is dynamically building class source as a string and calling exec() on it generally discouraged compared to type()?
It's harder to debug (errors reference a synthetic string, not a real file and line number), harder to read, and dangerous if any part of the assembled source is influenced by untrusted input; type()/types.new_class() build the same class directly from ordinary Python objects (string, tuple, dict) with no code-string assembly or execution involved.
Is calling type(name, bases, namespace) itself a security risk?
Not inherently — its arguments are ordinary Python objects. The risk arises only when those arguments (the name, base classes, or namespace contents) are derived from untrusted input in a way that lets an attacker influence the resulting class's identity or behavior; validate/allowlist such input before use.
When should you prefer dynamic class creation over an explicit class statement?
Only when the actual set of classes needed is genuinely determined by data unknown at write time (e.g. building model classes from a database schema or config file) — not as a shortcut to avoid writing a few explicit classes, since dynamic classes are harder for readers, IDEs, and type checkers to follow.
Write a function make_dataclass_like(name, fields) that uses type() to build a class with an __init__ setting each field name in fields (a list of strings) as an instance attribute from a same-named keyword argument.
def make_dataclass_like(name: str, fields: list[str]) -> type: def __init__(self, **kwargs): for field in fields: setattr(self, field, kwargs.get(field)) return type(name, (), {"__init__": __init__}) Point = make_dataclass_like("Point", ["x", "y"]) p = Point(x=1, y=2) print(p.x, p.y) # 1 2
A closure captures fields; type(name, (), {"__init__": __init__}) builds a brand-new class whose __init__ loops over the captured field names and sets each as an instance attribute from the matching keyword argument.
What does this print?
hi from B then True — B inherits greet from A via the normal MRO (bases work exactly like class B(A): pass), and type(self).__name__ reports the actual runtime class B; isinstance(b, A) is True because B's bases tuple includes A.
Write a factory function make_singleton(cls) that returns a NEW class (built with type()) wrapping cls so that calling it always returns the same instance, using a mutable default in the namespace dict to hold the cached instance.
def make_singleton(cls: type) -> type: instances = {} def __new__(new_cls, *args, **kwargs): if new_cls not in instances: instances[new_cls] = cls(*args, **kwargs) return instances[new_cls] return type(f"Singleton{cls.__name__}", (cls,), {"__new__": __new__}) class Service: def __init__(self): print("creating Service") SingletonService = make_singleton(Service) a = SingletonService() b = SingletonService() print(a is b) # True -- creating Service printed only once
type() builds a subclass of cls whose __new__ checks a closure-captured instances dict; the first call constructs and caches the real instance, and every subsequent call returns the cached one, so a is b is True and Service.__init__ runs only once.
def greet(self):
return f"hi from {type(self).__name__}"
A = type("A", (), {"greet": greet})
B = type("B", (A,), {})
b = B()
print(b.greet())
print(isinstance(b, A))