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
- Remove string formatting from the annotation: use real subscripts like list[T] instead of f"list[{T}]".
- If the value must be computed, compute it before the annotation and reference the result, or keep it a plain string literal.
- 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
- Never use f-strings or .format() inside annotations
- Prefer Format.VALUE or FORWARDREF when consuming third-party annotations
- Lint for JoinedStr nodes inside annotation positions in CI
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
- The VALUE_WITH_FAKE_GLOBALS format is for internal use only
- annotate function does not support VALUE format
- {obj!r}.__annotate__ returned a non-dict
- Forward reference must be an expression -- got {arg!r}
- Invalid format: {format!r}
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/532deee1e038b43d.
Report an issue: GitHub.