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.

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)

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.