python/cpython · error · TypeError

Cannot stringify annotation containing string formatting

Error message

Cannot stringify annotation containing string formatting

What it means

When annotations are retrieved in Format.STRING, every name is replaced by a _Stringifier object that records operations as AST. _Stringifier deliberately raises TypeError from __format__ because an f-string (or .format()/format()) inside an annotation cannot be faithfully represented as source text. Hitting it means an annotation uses string formatting, which annotationlib refuses to stringify.

Solutions

  1. Remove string formatting from the annotation: use real subscripts like list[T] instead of f"list[{T}]".
  2. If the value must be computed, compute it before the annotation and reference the result, or keep it a plain string literal.
  3. Consume annotations in Format.VALUE or FORWARDREF instead of STRING if you cannot change the annotated code.

Example fix

# before
T = int
x: f"list[{T.__name__}]" = []
annotationlib.get_annotations(mod, format=Format.STRING)  # TypeError

# after
x: "list[int]" = []
Defensive patterns

Strategy: validation

Validate before calling

import inspect, ast

def annotation_uses_formatting(source: str) -> bool:
    try:
        tree = ast.parse(source or '', mode='eval')
    except SyntaxError:
        return False
    return any(isinstance(n, (ast.JoinedStr, ast.FormattedValue)) for n in ast.walk(tree))

Type guard

from annotationlib import Format

def safe_string_annotations(obj) -> dict | None:
    try:
        return annotationlib.get_annotations(obj, format=Format.STRING)
    except TypeError:
        return None

Try / catch

try:
    ann = annotationlib.get_annotations(obj, format=Format.STRING)
except TypeError as e:
    if 'string formatting' in str(e):
        ann = annotationlib.get_annotations(obj, format=Format.FORWARDREF)
    else:
        raise

Prevention

When it happens

Trigger: A module using `from __future__ import annotations` with an f-string inside an annotation, e.g. `x: f"list[{T}]"`; calling annotationlib.get_annotations(obj, format=Format.STRING) or annotations_to_string on such an object; inspect.get_annotations on a class whose annotate function formats strings.

Common situations: Trying to parametrize annotations dynamically with f-strings instead of Subscription; tooling (docs generators, serializers) that stringifies annotations of third-party code that uses f-strings; migration to PEP 649 lazy annotations where STRING format is now used.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/532deee1e038b43d. Report an issue: GitHub.

Appendix: source

Thrown at Lib/annotationlib.py:497

            ast_args.append(new_arg)
        ast_kwargs = []
        for key, value in kwargs.items():
            new_value, new_extra_names = self.__convert_to_ast(value)
            if new_extra_names is not None:
                extra_names.update(new_extra_names)
            ast_kwargs.append(ast.keyword(key, new_value))
        return self.__make_new(ast.Call(self.__get_ast(), ast_args, ast_kwargs), extra_names)

    def __iter__(self):
        yield self.__make_new(ast.Starred(self.__get_ast()))

    def __repr__(self):
        if isinstance(self.__ast_node__, str):
            return self.__ast_node__
        return ast.unparse(self.__ast_node__)

    def __format__(self, format_spec):
        raise TypeError("Cannot stringify annotation containing string formatting")

    def _make_binop(op: ast.AST):
        def binop(self, other):
            rhs, extra_names = self.__convert_to_ast(other)
            return self.__make_new(
                ast.BinOp(self.__get_ast(), op, rhs), extra_names
            )

        return binop

    __add__ = _make_binop(ast.Add())
    __sub__ = _make_binop(ast.Sub())
    __mul__ = _make_binop(ast.Mult())
    __matmul__ = _make_binop(ast.MatMult())
    __truediv__ = _make_binop(ast.Div())
    __mod__ = _make_binop(ast.Mod())
    __lshift__ = _make_binop(ast.LShift())
    __rshift__ = _make_binop(ast.RShift())

View on GitHub (pinned to bc6749cc3b)