types Module Complexity¶
The types module names the interpreter's types that have no builtin name - functions, generators,
modules, frames, descriptors - and adds a few utilities: SimpleNamespace, the read-only
MappingProxyType, and helpers that create classes the way a class statement does. Using a type
name is an attribute read; the costs on this page are in the utilities and the constructors.
n is the entries in a namespace or mapping, and m the entries in the other operand of | or
the changes passed to copy.replace(). d is the length of type(obj).__mro__, c is a
function's closure cells, and k is the size of a function's code object. b is the bases given to
the class-creation helpers or the bases __mro_entries__ turns them into, whichever is more; each
base replaced by more or fewer than one adds another O(b) to resolve_bases() and new_class().
Key hashing and key and value comparison count as O(1), as do class keyword arguments. Proxy bounds
assume the proxied mapping is a dict; the proxy adds O(1) to whatever its mapping costs.
Complexity Reference¶
Type names¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.FunctionType, types.LambdaType, types.MethodType, types.CodeType, types.CellType |
O(1) | O(1) | Functions written in Python and their parts; LambdaType is FunctionType |
types.GeneratorType, types.CoroutineType, types.AsyncGeneratorType |
O(1) | O(1) | What calling a generator, async def or asynchronous generator function returns |
types.BuiltinFunctionType, types.BuiltinMethodType, types.WrapperDescriptorType, types.MethodWrapperType, types.MethodDescriptorType, types.ClassMethodDescriptorType |
O(1) | O(1) | Callables written in C; BuiltinMethodType is BuiltinFunctionType |
types.GetSetDescriptorType, types.MemberDescriptorType |
O(1) | O(1) | The descriptors behind attributes of types written in C |
types.ModuleType, types.TracebackType, types.FrameType |
O(1) | O(1) | |
types.NoneType, types.EllipsisType, types.NotImplementedType |
O(1) | O(1) | The types of the three singletons |
types.GenericAlias, types.UnionType |
O(1) | O(1) | What list[int] and int \| str build; UnionType is typing.Union from Python 3.14 |
types.CapsuleType |
O(1) | O(1) | Python 3.13+ |
isinstance(obj, types.FunctionType), or any name above |
O(d) | O(1) | An exact type match is one comparison; see isinstance |
Constructors¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.FunctionType(code, globals, name=None, argdefs=None, closure=None) |
O(c) | O(1) | Shares the code, globals, defaults and closure; each closure cell is checked once |
types.MethodType(function, instance) |
O(1) | O(1) | Binds a callable to an instance, as attribute lookup does |
types.ModuleType(name, doc=None) |
O(1) | O(1) | An empty module: nothing is imported and sys.modules is not touched |
types.TracebackType(tb_next, tb_frame, tb_lasti, tb_lineno) |
O(1) | O(1) | Links one entry onto an existing chain |
types.GenericAlias(t_origin, t_args) |
O(1) | O(1) | Keeps the t_args tuple itself |
SimpleNamespace¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.SimpleNamespace(**kwargs), types.SimpleNamespace(mapping_or_iterable, /, **kwargs) |
O(n) | O(n) | Copies the entries into its own dict, even from a dict; the positional form is Python 3.13+ |
ns.attr, ns.attr = value, del ns.attr |
O(1) | O(1) | Average case: one dict operation |
vars(ns), ns.__dict__ |
O(1) | O(1) | The namespace's own dict, not a copy: writing to it changes the namespace |
ns == other |
O(n) | O(1) | Compares the two dicts when both sides are namespaces |
repr(ns) |
O(n) | O(n) | Plus each value's repr() |
copy.replace(ns, **changes) |
O(n + m) | O(n + m) | A new namespace; Python 3.13+ |
MappingProxyType¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.MappingProxyType(mapping) |
O(1) | O(1) | Copies nothing, so later changes to mapping show through; a list or tuple is rejected |
proxy[key], key in proxy, MappingProxyType.get(key, default=None) |
O(1) | O(1) | Average case |
len(proxy) |
O(1) | O(1) | |
iter(proxy), reversed(proxy) |
O(n) | O(1) | For the full pass |
MappingProxyType.keys(), MappingProxyType.values(), MappingProxyType.items() |
O(1) | O(1) | The mapping's own views, not lists |
MappingProxyType.copy() |
O(n) | O(n) | The mapping's copy(): a writable dict for a dict, not a proxy |
proxy \| other, other \| proxy |
O(n + m) | O(n + m) | A new dict; \|= raises TypeError |
proxy == other |
O(n) | O(1) | Compared as the mapping |
Dynamic class creation¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.new_class(name, bases=(), kwds=None, exec_body=None) |
O(b + n) | O(b + n) | n = entries exec_body leaves in the namespace; exec_body, the metaclass and the MRO cost what they cost in a class statement |
types.prepare_class(name, bases=(), kwds=None) |
O(b) | O(1) | Picks the most derived metaclass and calls its __prepare__; copies kwds rather than changing it |
types.resolve_bases(bases) |
O(b) | O(b) | Calls __mro_entries__ where a base defines it; returns bases itself when none does |
types.get_original_bases(cls, /) |
O(1) | O(1) | The stored __orig_bases__, or __bases__; Python 3.12+ |
Coroutines and descriptors¶
| Operation | Time | Space | Notes |
|---|---|---|---|
types.coroutine(gen_func) |
O(k) | O(k) | Returns the same generator function with a coroutine-flagged copy of its code object; a coroutine function comes back unchanged, and any other callable is wrapped |
types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None) |
O(1) | O(1) | A property on instances; on the class it raises AttributeError so the metaclass's __getattr__ answers, or returns itself if the getter is abstract |
Checking Types¶
The names exist for isinstance() checks against types that have no builtin name. They answer a
narrower question than callable(): a function written in C is not a FunctionType.
import types
def plain():
pass
class Widget:
def method(self):
pass
assert isinstance(plain, types.FunctionType) # exact type: one comparison
assert isinstance(lambda: None, types.LambdaType) # the same type
assert isinstance(Widget().method, types.MethodType) # a bound method
assert isinstance(Widget.__dict__['method'], types.FunctionType) # unbound, it is a function
assert isinstance(len, types.BuiltinFunctionType) # written in C
assert not isinstance(len, types.FunctionType)
assert callable(len) and callable(plain)
assert isinstance(None, types.NoneType)
assert isinstance(list[int], types.GenericAlias)
SimpleNamespace¶
Construction Copies, vars() Does Not¶
Building a namespace copies its entries into a dict of its own, so it does not follow the dict it
was built from. vars() hands back that dict itself, at no cost.
import types
settings = {'host': 'localhost', 'port': 8080}
ns = types.SimpleNamespace(**settings) # O(n) - copies the entries
settings['port'] = 9090
assert ns.port == 8080 # the namespace has its own dict
assert vars(ns) is ns.__dict__ # O(1) - no copy
vars(ns)['debug'] = True # so writing to it changes the namespace
assert ns.debug is True
ns.port = 443 # O(1)
del ns.host # O(1)
assert ns == types.SimpleNamespace(port=443, debug=True) # O(n)
assert ns != {'port': 443, 'debug': True} # a namespace never equals a dict
assert repr(ns) == 'namespace(port=443, debug=True)' # O(n)
MappingProxyType¶
A Live View, Not a Copy¶
A proxy wraps the mapping it is given, so building one costs nothing and every later change to the
mapping shows through. The two operations that produce something new, copy() and |, return a
plain dict.
import types
registry = {'a': 1, 'b': 2}
view = types.MappingProxyType(registry) # O(1) - wraps, copies nothing
registry['c'] = 3
assert view['c'] == 3 and len(view) == 3 # O(1) - changes show through
assert list(view.keys()) == ['a', 'b', 'c'] # keys() is O(1); list() is O(n)
try:
view['d'] = 4
except TypeError as error:
assert 'does not support item assignment' in str(error)
else:
raise AssertionError('a proxy accepted an assignment')
snapshot = view.copy() # O(n) - a plain dict
snapshot['d'] = 4
assert 'd' not in view
merged = view | {'e': 5} # O(n + m) - also a plain dict
assert type(merged) is dict and 'e' not in view
Building Functions and Classes Dynamically¶
FunctionType¶
A function built from an existing code object shares that code object, its globals and its closure cells. Two functions built over the same cells see each other's writes.
import types
def scale(x, factor=2):
return x * factor
clone = types.FunctionType(scale.__code__, scale.__globals__, 'clone', (10,)) # O(c)
assert clone(3) == 30
assert clone.__code__ is scale.__code__ # shared, not copied
assert clone.__name__ == 'clone'
def counter():
count = 0
def bump():
nonlocal count
count += 1
return count
return bump
bump = counter()
twin = types.FunctionType(bump.__code__, bump.__globals__, closure=bump.__closure__) # O(c)
assert bump() == 1
assert twin() == 2 # the same cell
new_class¶
new_class() is the functional form of a class statement. It resolves __mro_entries__ and calls
the metaclass's __prepare__ the way the statement does, which a direct call to type() does not.
import types
def body(ns):
ns['greeting'] = 'hello'
Scores = types.new_class('Scores', (list[int],), exec_body=body) # O(b + n)
assert Scores.__bases__ == (list,) # list[int] resolved to list
assert Scores.__orig_bases__ == (list[int],) # the original is kept
assert Scores().greeting == 'hello'
try:
type('Scores', (list[int],), {})
except TypeError as error:
assert 'new_class' in str(error)
else:
raise AssertionError('type() accepted a generic alias base')
meta, ns, kwds = types.prepare_class('Plain', (), {'metaclass': type}) # O(b)
assert meta is type and ns == {} and kwds == {}
bases = (int,)
assert types.resolve_bases(bases) is bases # O(b) - nothing to resolve, the same tuple
Coroutine Adapters¶
types.coroutine() marks a generator function as awaitable. It changes the function it is given
rather than wrapping it, so the cost is paid once, at decoration.
import types
def ticker():
yield
marked = types.coroutine(ticker) # O(k), once
assert marked is ticker # the same function, now awaitable
async def main():
await ticker()
return 'done'
coro = main()
coro.send(None) # the bare yield suspends main()
try:
coro.send(None)
except StopIteration as stop:
assert stop.value == 'done'
else:
raise AssertionError('main() did not finish')
Common Patterns¶
Read-Only Defaults¶
import types
_DEFAULTS = {'timeout': 30, 'retries': 3}
DEFAULTS = types.MappingProxyType(_DEFAULTS) # O(1)
def effective(overrides):
return DEFAULTS | overrides # O(n + m) - a fresh dict each call
assert effective({'retries': 5}) == {'timeout': 30, 'retries': 5}
assert DEFAULTS['retries'] == 3
Attribute Access over Parsed Data¶
import json
import types
text = '{"db": {"host": "localhost", "port": 5432}, "debug": false}'
config = json.loads(text, object_hook=lambda d: types.SimpleNamespace(**d)) # O(n) per object
assert config.db.port == 5432 # O(1) per attribute
assert config.debug is False
Performance Best Practices¶
✅ Do:
- Expose a dict read-only with
MappingProxyType: O(1), and it stays current wheredict(d)is an O(n) snapshot - Use
vars(ns)when a namespace is needed as a mapping; it is the namespace's own dict, O(1) - Use
types.new_class()when bases or the metaclass are only known at run time; it runs__mro_entries__and__prepare__the way aclassstatement does
❌ Avoid:
dict(proxy)orproxy.copy()just to read; the proxy already answers lookups in O(1)- Treating a proxy as a snapshot: it follows every later change to its mapping
- Expecting a namespace built from a dict to track that dict: construction copied it
Version Notes¶
- Python 3.11+:
types.coroutine()copies a generator function's bytecode when it swaps the code object, O(k); 3.10 shares it - Python 3.12+: Added
get_original_bases() - Python 3.13+: Added
CapsuleType;SimpleNamespaceaccepts a mapping or an iterable of pairs as a positional argument, and supportscopy.replace() - Python 3.14+:
UnionTypeistyping.Union, sotyping.Union[int, str]is an instance too
Related Modules¶
- inspect -
isfunction(),ismethod()and the other predicates test against these types - typing - annotations;
list[int]builds aGenericAliasandint | straUnionType - collections.abc - check for a protocol instead of a concrete type
- dataclasses - a declared record type where
SimpleNamespaceis too loose