Python types: Runtime Type Utilities

Published on: August 6, 2026
Reading time: 5 minutes
Code and structures representing runtime types with the Python types module

Functions, methods, modules, generators, frames, code objects, and generic aliases are real runtime objects in Python. The Python types module provides standard names for many of these structures and utilities for dynamic class creation, namespace preparation, generator-to-coroutine adaptation, and read-only mapping views.

This guide covers SimpleNamespace, MappingProxyType, MethodType, new_class(), prepare_class(), resolve_bases(), and the runtime types exposed by the module. It complements our articles about Python inspect, bytecode with dis, symtable, contextvars, and copyreg.

Why the types module exists

Many internal types can be discovered with type(), but expressions such as type(lambda: None) are unclear in production code. The module provides explicit names such as FunctionType, GeneratorType, CoroutineType, and TracebackType.

import types

print(isinstance(lambda: None, types.FunctionType))
print(isinstance((x for x in range(3)), types.GeneratorType))

These names help introspection, debuggers, frameworks, and low-level validation. Business logic should often prefer protocols and collections.abc when behavior matters more than the exact implementation type.

SimpleNamespace

SimpleNamespace creates a small object whose attributes are stored in an internal dictionary.

from types import SimpleNamespace

config = SimpleNamespace(
    host="localhost",
    port=8000,
    debug=True,
)

print(config.host)
config.port = 9000

It is convenient for small results, temporary configuration, mocks, and grouping values. Its representation displays attributes, and equality compares namespace contents.

SimpleNamespace is not a domain model

The object validates no fields, imposes no types, and freely accepts new attributes.

config.prt = 7000  # typo is accepted

When invariants, documentation, methods, or validation matter, choose a dataclass, NamedTuple, TypedDict, or ordinary class.

MappingProxyType

MappingProxyType creates a dynamic read-only view of a mapping.

from types import MappingProxyType

source = {"mode": "production", "retries": 3}
public = MappingProxyType(source)

print(public["mode"])
# public["mode"] = "test"  # TypeError

The proxy prevents changes through the public reference but reflects updates made to the original mapping.

source["retries"] = 5
print(public["retries"])  # 5

It provides read-only access, not deep immutability.

When to use MappingProxyType

It is useful for exposing registries, metadata, internal configuration, and dispatch tables without returning a mutable mapping.

_handlers = {"json": process_json}
handlers = MappingProxyType(_handlers)

Nested values can still be mutable. If the mapping contains lists, callers may mutate those lists. Copy or freeze nested values when necessary.

MethodType

MethodType binds a function to an instance, producing a bound method.

from types import MethodType

class User:
    def __init__(self, name):
        self.name = name


def greet(self):
    return f"Hello, {self.name}"

user = User("Ana")
user.greet = MethodType(greet, user)
print(user.greet())

This appears in tests, instrumentation, and plugin systems. Permanent behavior is usually clearer through composition or subclasses.

FunctionType and LambdaType

FunctionType represents Python-defined functions. LambdaType is an alias for the same type.

import types


def add(a, b):
    return a + b

assert isinstance(add, types.FunctionType)
assert types.LambdaType is types.FunctionType

Built-ins such as len use BuiltinFunctionType, while built-in methods may use BuiltinMethodType.

Generator, coroutine, and async-generator types

import types


def numbers():
    yield 1

async def fetch():
    return 42

async def events():
    yield "start"

generator = numbers()
coroutine = fetch()
async_generator = events()

print(isinstance(generator, types.GeneratorType))
print(isinstance(coroutine, types.CoroutineType))
print(isinstance(async_generator, types.AsyncGeneratorType))

Created coroutines must be awaited or closed to avoid warnings.

types.coroutine()

types.coroutine() turns a generator function into an await-compatible coroutine. It mainly supports low-level interoperability between generator-based and native async systems.

import types

@types.coroutine
def wait_for_event():
    result = yield "event"
    return result

Modern applications should normally use async def. The decorator remains useful to runtime libraries and legacy adapters.

ModuleType

ModuleType creates module objects.

from types import ModuleType

module = ModuleType("my_module", "Dynamically created module")
module.value = 42

print(module.__name__)
print(module.value)

For modules that participate in imports, the official importlib documentation recommends specs and importlib.util.module_from_spec(), which initializes more attributes correctly.

Create classes with new_class()

new_class() implements the modern dynamic-class creation protocol.

import types


def fill(namespace):
    namespace["category"] = "dynamic"

    def describe(self):
        return self.category

    namespace["describe"] = describe

Dynamic = types.new_class(
    "Dynamic",
    bases=(object,),
    exec_body=fill,
)

print(Dynamic().describe())

The helper respects metaclasses, __prepare__(), and base resolution instead of requiring applications to reproduce that protocol manually.

new_class() parameters

The first argument is the class name; bases contains base classes; kwds can include metaclass; and exec_body receives the prepared namespace.

Plugin = types.new_class(
    "Plugin",
    bases=(BasePlugin,),
    kwds={"metaclass": PluginMeta},
    exec_body=lambda ns: ns.update({"version": 1}),
)

Validate names, bases, and metaclasses that come from configuration. Dynamic class creation is not a sandbox.

prepare_class()

prepare_class() calculates the appropriate metaclass and prepares a namespace before class creation.

meta, namespace, keywords = types.prepare_class(
    "MyClass",
    (Base,),
    {"metaclass": MyMeta},
)

namespace["attribute"] = 10
MyClass = meta("MyClass", (Base,), namespace, **keywords)

Frameworks can inspect or populate the namespace between preparation and construction.

resolve_bases()

Class bases may include objects implementing __mro_entries__() instead of real types. resolve_bases() applies this protocol.

resolved = types.resolve_bases(original_bases)

This matters for generic aliases and abstractions used in a class’s base list. Use resolved bases when invoking a metaclass directly.

get_original_bases()

get_original_bases() retrieves bases declared before resolution when metadata was preserved in __orig_bases__.

from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
    pass

class TextBox(Box[str]):
    pass

print(types.get_original_bases(TextBox))

Typing frameworks can use this to recover generic parameters. Missing metadata should be treated as a normal case.

DynamicClassAttribute

DynamicClassAttribute creates a descriptor similar to property but permits different handling during class-level access.

It is used by implementations such as enum. Ordinary application code should usually prefer property.

CodeType

CodeType represents compiled code objects.

code = compile("x = 1", "<example>", "exec")
print(isinstance(code, types.CodeType))

The CodeType constructor changes across Python versions and exposes many interpreter details. Prefer compile() and code.replace().

new_code = code.replace(co_filename="virtual_file.py")

Executing a code object remains code execution and should never use unknown input.

FrameType and TracebackType

FrameType represents execution frames; TracebackType represents an exception’s traceback chain.

try:
    1 / 0
except ZeroDivisionError as error:
    tb = error.__traceback__
    print(isinstance(tb, types.TracebackType))
    print(isinstance(tb.tb_frame, types.FrameType))

Frames retain references to local values and can extend object lifetimes. Release references after diagnostics.

GenericAlias and UnionType

GenericAlias represents expressions such as list[int]. UnionType represents unions produced with |.

alias = list[int]
union = int | str

print(isinstance(alias, types.GenericAlias))
print(isinstance(union, types.UnionType))

These objects support annotation introspection but do not validate runtime values.

The module provides names such as NoneType, EllipsisType, and NotImplementedType.

print(isinstance(None, types.NoneType))
print(isinstance(Ellipsis, types.EllipsisType))
print(isinstance(NotImplemented, types.NotImplementedType))

This avoids expressions such as type(None) in introspection APIs.

Extension descriptors

GetSetDescriptorType and MemberDescriptorType represent descriptors commonly created by C extensions and __slots__.

class Example:
    __slots__ = ("value",)

print(isinstance(Example.value, types.MemberDescriptorType))

Some runtimes may use the same internal type for both descriptor categories. Test supported implementations before relying on the distinction.

Security and introspection

The module exposes powerful runtime structures. Creating classes, modules, methods, metaclasses, or code objects does not validate their origin.

Never execute functions, metaclasses, bases, or bytecode supplied by users. Plugin systems need explicit interfaces, authorization, and isolation.

Common mistakes

  • Using SimpleNamespace for data requiring validation.
  • Treating MappingProxyType as deep immutability.
  • Attaching methods dynamically without documenting behavior.
  • Constructing CodeType manually.
  • Accepting untrusted metaclasses or bases.
  • Assuming annotations validate values.
  • Keeping frames and tracebacks indefinitely.
  • Using exact type checks when a protocol is better.

Best practices

  • Use types names for clear introspection.
  • Prefer dataclasses for structured models.
  • Expose read-only proxies for public mappings.
  • Create dynamic classes with new_class().
  • Use module_from_spec() for imports.
  • Prefer async def to legacy adapters.
  • Release frames after analysis.
  • Test compatibility across Python versions.

Conclusion

The Python types module gives explicit names to runtime structures and utilities for namespaces, read-only mappings, bound methods, and dynamic classes. It is especially useful in frameworks, debuggers, and introspection tools.

These APIs operate close to language internals. By preferring simpler abstractions when possible, validating plugins and metaclasses, and avoiding unstable constructors, developers can use runtime flexibility without making applications fragile.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python sched: Schedule Events

    Learn Python sched to schedule events, control priorities, cancel tasks, and build recurring work with a monotonic clock.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python atexit: Run Cleanup on Exit

    Learn Python atexit to run cleanup at shutdown, control LIFO order, and avoid problems with threads, signals, and handler exceptions.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Monitor with binary code representing pickle opcode analysis with Python pickletools
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pickletools: Analyze Pickles

    Learn Python pickletools to disassemble pickles, inspect opcodes, and optimize streams without executing untrusted data.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Source code on screen representing analysis with Python tokenize
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tokenize: Analyze Source Code

    Learn Python tokenize to inspect tokens, comments, indentation, encodings, source positions, and rebuild code safely.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Developer working on build automation and directory compilation with Python compileall
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python compileall: Compile Directories

    Learn Python compileall to compile directories, generate pyc files in parallel, filter paths, and control optimization and invalidation.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026
    Monitor with binary code representing pyc generation with Python py_compile
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python py_compile: Generate pyc Files

    Learn Python py_compile to generate pyc files, validate syntax, and control optimization and timestamp or hash invalidation.

    Ler mais

    Tempo de leitura: 5 minutos
    04/08/2026