Skip to content

56. `types`

types.FunctionType, types.CodeType, types.SimpleNamespace, and dynamic type creation with types.new_class.

The types module exposes names for many runtime object types used internally by CPython. It gives Python code a stable way to ask, “What kind of runtime object is this?” without hard-coding obscure expressions such as type(lambda: None) or type((x for x in [])).

The module is small, but it sits close to the object model. It names function objects, method objects, module objects, frame objects, code objects, generator objects, coroutine objects, mapping proxy objects, descriptors, namespace objects, and several helper classes used by the runtime.

56.1 The Role of types

types is a catalog of runtime object classes.

Example:

import types

def f():
    pass

print(isinstance(f, types.FunctionType))
print(isinstance(f.__code__, types.CodeType))

Output:

True
True

Conceptually:

runtime object
type(obj)
named runtime type from types

The module is useful when writing tools that need to distinguish between Python-level object categories:

debuggers
profilers
serializers
documentation generators
import tools
decorator frameworks
plugin loaders
mocking libraries
runtime validators

56.2 types vs type

The built-in type() function returns the concrete type of an object.

def f():
    pass

print(type(f))

Output shape:

<class 'function'>

The types module gives a named reference to that same type:

import types

print(type(f) is types.FunctionType)

Output:

True

So this:

type(f) is type(lambda: None)

can be written more clearly as:

type(f) is types.FunctionType

Most code should prefer isinstance() over exact type checks unless exact runtime layout matters.

56.3 Function Types

A Python function object has type types.FunctionType.

import types

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

print(isinstance(add, types.FunctionType))

A function object wraps:

FieldMeaning
__code__Code object
__globals__Global namespace
__defaults__Positional defaults
__kwdefaults__Keyword-only defaults
__closure__Closure cells
__annotations__Annotations
__dict__Custom attributes
__name__Function name
__qualname__Qualified name

Internally, this corresponds to CPython’s function object implementation.

Conceptually:

function object
    code object
    globals
    defaults
    closure

types.FunctionType can also construct a new function from a code object.

import types

def template():
    return answer

code = template.__code__
namespace = {"answer": 42}

fn = types.FunctionType(code, namespace)
print(fn())

Output:

42

This shows that a function combines compiled code with a global namespace.

56.4 Code Objects

Code objects have type types.CodeType.

import types

def f(x):
    return x + 1

print(isinstance(f.__code__, types.CodeType))

A code object is immutable compiled program data.

It contains:

bytecode
constants
names
local variable names
free variable names
cell variable names
source filename
line information
exception table
stack size
flags

Code objects do not store runtime local values. They describe executable code.

code = f.__code__

print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)

A function object points to a code object. A frame executes a code object. The dis module decodes a code object.

source code
compiler
code object
function object
frame execution

56.5 Creating Modified Code Objects

Code objects are immutable, but modern Python provides code.replace().

def f():
    return 1

code = f.__code__
new_code = code.replace(co_name="renamed")

print(new_code.co_name)

Output:

renamed

This is safer than manually calling types.CodeType(...), because the constructor signature changes across Python versions.

Use cases include:

debugging tools
coverage tools
bytecode experiments
code generation systems
function wrappers
educational bytecode work

For production code, code object mutation should be treated as CPython-version-sensitive.

56.6 Method Types

Bound Python methods have type types.MethodType.

import types

class User:
    def name(self):
        return "anonymous"

u = User()

print(isinstance(u.name, types.MethodType))

A bound method packages:

function
instance

That is:

print(u.name.__func__)
print(u.name.__self__)

Conceptually:

obj.method
descriptor lookup
bound method(function, obj)

Calling:

u.name()

is equivalent in effect to:

User.name(u)

types.MethodType can also bind a function to an object manually:

import types

class User:
    pass

def greet(self):
    return "hello"

u = User()
u.greet = types.MethodType(greet, u)

print(u.greet())

Output:

hello

56.7 Built-in Function and Method Types

C-level built-ins have separate runtime types.

import types

print(isinstance(len, types.BuiltinFunctionType))
print(isinstance([].append, types.BuiltinMethodType))

Built-in functions usually have no Python code object:

print(hasattr(len, "__code__"))

Output:

False

They are implemented in C and exposed as callable Python objects.

Conceptually:

Python call
built-in callable object
C function pointer

This distinction matters for tools. A debugger or signature extractor cannot inspect a built-in the same way it inspects a Python function.

56.8 Module Type

Modules have type types.ModuleType.

import types
import math

print(isinstance(math, types.ModuleType))

You can create a module object directly:

import types

mod = types.ModuleType("demo")
mod.answer = 42

print(mod.answer)

A module object is mostly a namespace with metadata:

AttributeMeaning
__name__Module name
__dict__Module namespace
__spec__Import specification
__loader__Loader object
__package__Package name
__file__Source or binary file path, when present

sys.modules stores module objects by name.

sys.modules["math"] → module object

56.9 Frame Type

Frame objects have type types.FrameType.

import sys
import types

frame = sys._getframe()

print(isinstance(frame, types.FrameType))

A frame represents active or suspended execution.

It exposes:

AttributeMeaning
f_codeCode object being executed
f_localsLocal variables
f_globalsGlobal variables
f_builtinsBuiltins
f_backCaller frame
f_linenoCurrent line
f_traceTrace function

Frames are used by:

tracebacks
debuggers
profilers
inspect.currentframe()
sys._getframe()
generators
coroutines

Holding a frame reference can keep local variables and caller frames alive. This makes frame objects important in memory debugging.

56.10 Traceback Type

Tracebacks have type types.TracebackType.

import types

try:
    1 / 0
except Exception as exc:
    tb = exc.__traceback__
    print(isinstance(tb, types.TracebackType))

A traceback is a linked list of execution records.

Each node contains:

AttributeMeaning
tb_frameFrame at this level
tb_linenoLine number
tb_lastiBytecode offset
tb_nextNext traceback node

Conceptually:

exception
traceback node
traceback node
...

Tracebacks retain frames. Frames retain locals. Saved exceptions can therefore retain large object graphs.

56.11 Generator Type

Generator objects have type types.GeneratorType.

import types

def gen():
    yield 1

g = gen()

print(isinstance(g, types.GeneratorType))

A generator object contains:

code object
suspended frame
running flag
delegated iterator

Important attributes include:

AttributeMeaning
gi_codeCode object
gi_frameSuspended frame
gi_runningRunning state
gi_yieldfromCurrent delegated iterator

A generator is a resumable execution frame.

call generator function
create generator object
start execution on next()
pause at yield
resume later

56.12 Coroutine Type

Native coroutine objects have type types.CoroutineType.

import types

async def fetch():
    return 1

coro = fetch()

print(isinstance(coro, types.CoroutineType))

coro.close()

A coroutine is similar to a generator, but it participates in the await protocol.

Important attributes include:

AttributeMeaning
cr_codeCode object
cr_frameSuspended frame
cr_runningRunning state
cr_awaitObject currently awaited

Coroutine objects are created by calling an async def function.

They do not execute immediately. They execute when awaited or driven by an event loop.

56.13 Async Generator Type

Async generators have type types.AsyncGeneratorType.

import types

async def agen():
    yield 1

obj = agen()

print(isinstance(obj, types.AsyncGeneratorType))

Async generators combine resumable execution with asynchronous iteration.

They are consumed with:

async for item in agen():
    ...

At the object model level, they have their own type because their protocol differs from ordinary generators and coroutines.

56.14 Cell Type

Closure cells have type types.CellType.

import types

def outer(x):
    def inner():
        return x
    return inner

fn = outer(10)
cell = fn.__closure__[0]

print(isinstance(cell, types.CellType))
print(cell.cell_contents)

A cell stores a variable captured by nested functions.

Conceptually:

outer local variable
cell object
inner function closure

This indirection lets a variable outlive the stack frame that originally created it.

56.15 Mapping Proxy Type

Class dictionaries are exposed as read-only mapping proxies.

import types

class User:
    kind = "user"

print(type(User.__dict__) is types.MappingProxyType)

A mapping proxy presents a dynamic read-only view over a dictionary.

proxy = User.__dict__

print(proxy["kind"])

User.role = "admin"
print(proxy["role"])

The proxy reflects updates to the underlying mapping, but does not allow direct mutation through the proxy.

proxy["x"] = 1

raises:

TypeError

This is used for type dictionaries because CPython wants controlled mutation paths for class objects.

56.16 Simple Namespace

types.SimpleNamespace is a small mutable object with attribute storage.

from types import SimpleNamespace

cfg = SimpleNamespace(host="localhost", port=5432)

print(cfg.host)
print(cfg.port)

It is roughly an object wrapper around a dictionary.

print(cfg.__dict__)

Output:

{'host': 'localhost', 'port': 5432}

It is useful for simple structured data, tests, configuration objects, and ad hoc namespaces.

Unlike a dataclass, it has no declared fields, no type constraints, and no generated methods beyond basic representation and comparison behavior.

56.17 Dynamic Class Creation

types.new_class() creates a class dynamically.

import types

User = types.new_class("User", (), {}, lambda ns: ns.update({
    "kind": "user",
}))

print(User.kind)

This mirrors the class creation protocol.

Conceptually:

resolve bases
prepare namespace
execute body callback
call metaclass
return class object

This is useful for metaprogramming tools that need to construct classes while respecting metaclasses and __prepare__.

56.18 Resolving Bases

types.resolve_bases() supports dynamic base classes.

Some objects can provide __mro_entries__ to change how they appear as bases.

Example concept:

class Alias:
    def __mro_entries__(self, bases):
        return (dict,)

class C(Alias()):
    pass

During class creation, CPython resolves the effective base classes before building the final type.

types.resolve_bases() exposes part of this mechanism.

This matters for generic aliases and advanced class construction tools.

56.19 Generic Alias

types.GenericAlias represents parameterized built-in collection types.

Example:

import types

alias = list[int]

print(type(alias) is types.GenericAlias)
print(alias.__origin__)
print(alias.__args__)

Output shape:

True
<class 'list'>
(<class 'int'>,)

This object supports runtime representation of expressions such as:

list[int]
dict[str, int]
tuple[int, ...]

These are mainly used by typing tools, annotation processing, and runtime introspection.

56.20 Union Type

The expression int | str creates a union type object.

import types

u = int | str

print(isinstance(u, types.UnionType))

This supports modern type annotation syntax.

def f(x: int | str):
    pass

Runtime tools can inspect the union through typing helpers such as typing.get_origin() and typing.get_args().

56.21 Descriptor Types

types exposes several descriptor-related types.

Examples include:

GetSetDescriptorType
MemberDescriptorType
MethodDescriptorType
WrapperDescriptorType
MethodWrapperType
ClassMethodDescriptorType

These names refer to C-level descriptor objects used by built-in and extension types.

Example:

import types

print(isinstance(str.upper, types.MethodDescriptorType))
print(isinstance(object.__str__, types.WrapperDescriptorType))

Descriptors are central to attribute access.

obj.attr
type lookup
descriptor protocol
bound result or raw value

The descriptor types exposed by types help introspection tools classify built-in behavior that has no Python function object.

56.22 DynamicClassAttribute

types.DynamicClassAttribute is used to route class-level attribute access through __getattr__ while preserving instance-level property behavior.

It is used by modules such as enum.

Simplified example:

from types import DynamicClassAttribute

class C:
    @DynamicClassAttribute
    def value(self):
        return 42

This is specialized machinery. Most code should use property.

Its importance is that types contains not only type names, but also helper objects needed by standard library internals.

56.23 prepare_class()

types.prepare_class() exposes the namespace preparation phase of class creation.

Class creation roughly follows:

choose metaclass
prepare namespace
execute class body
create class object

A metaclass may define __prepare__:

class Meta(type):
    @classmethod
    def __prepare__(cls, name, bases):
        return {}

types.prepare_class() performs the early metaclass and namespace resolution steps.

This is useful when implementing dynamic class factories that need to match normal class statement behavior.

56.24 Runtime Type Names as Documentation

Many objects used by CPython are easier to understand once named.

Runtime expressiontypes name
type(lambda: None)FunctionType
type((lambda: None).__code__)CodeType
type((x for x in []))GeneratorType
type(sys._getframe())FrameType
type(module)ModuleType
type(cls.__dict__)MappingProxyType

These names make introspection code clearer and less fragile.

56.25 Relationship to inspect

types and inspect often work together.

types gives concrete runtime type names.

inspect gives higher-level predicates and metadata extraction.

Example:

import inspect
import types

def f():
    pass

print(type(f) is types.FunctionType)
print(inspect.isfunction(f))

The inspect version usually expresses intent better. The types version gives direct type identity.

Use inspect when asking semantic questions. Use types when exact runtime type objects matter.

56.26 Relationship to CPython Internals

The types module reflects the object taxonomy of CPython.

It names objects created by core runtime systems:

CPython subsystemRuntime types exposed through types
CompilerCodeType
Function runtimeFunctionType, MethodType
Execution engineFrameType, TracebackType
Generators and asyncGeneratorType, CoroutineType, AsyncGeneratorType
Import systemModuleType
Object modeldescriptor types, MappingProxyType
Class creationnew_class, prepare_class, resolve_bases
Typing runtimeGenericAlias, UnionType

This makes types a compact map of CPython’s internal object system.

56.27 Common Mistakes

Avoid exact type checks when subclassing or protocol behavior matters.

Less flexible:

import types

def accepts_function(fn):
    return type(fn) is types.FunctionType

More flexible:

def accepts_callable(fn):
    return callable(fn)

Exact type checks reject callable objects, bound methods, built-ins, partial functions, and objects with __call__.

Use exact runtime types only when you really need the concrete object category.

Another mistake is assuming built-ins behave like Python functions:

len.__code__

This fails because len is a built-in function implemented in C.

56.28 Chapter Summary

The types module names CPython runtime object types. It exposes function types, code objects, modules, frames, tracebacks, generators, coroutines, closure cells, descriptors, mapping proxies, generic aliases, union types, and class creation helpers.

For CPython internals, types is useful because it gives Python-level names to objects created by the compiler, interpreter, import system, function runtime, object model, and async machinery.