56. `types`

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

56. types

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:

Field Meaning
__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:

Attribute Meaning
__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:

Attribute Meaning
f_code Code object being executed
f_locals Local variables
f_globals Global variables
f_builtins Builtins
f_back Caller frame
f_lineno Current line
f_trace Trace 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:

Attribute Meaning
tb_frame Frame at this level
tb_lineno Line number
tb_lasti Bytecode offset
tb_next Next 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:

Attribute Meaning
gi_code Code object
gi_frame Suspended frame
gi_running Running state
gi_yieldfrom Current 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:

Attribute Meaning
cr_code Code object
cr_frame Suspended frame
cr_running Running state
cr_await Object 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 expression types 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 subsystem Runtime types exposed through types
Compiler CodeType
Function runtime FunctionType, MethodType
Execution engine FrameType, TracebackType
Generators and async GeneratorType, CoroutineType, AsyncGeneratorType
Import system ModuleType
Object model descriptor types, MappingProxyType
Class creation new_class, prepare_class, resolve_bases
Typing runtime GenericAlias, 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.