python/cpython · error · ValueError
{obj!r}.__annotations__ is neither a dict nor None
Error message
{obj!r}.__annotations__ is neither a dict nor None What it means
_get_dunder_annotations() validates that obj.__annotations__, when present, is either a dict or None; any other type raises ValueError. This catches objects (or classes) whose __annotations__ was overwritten with, say, a list, a string, or a property, which would otherwise silently corrupt downstream consumers.
Source
Thrown at Lib/annotationlib.py:1162
Does not return a fresh dictionary.
"""
# This special case is needed to support types defined under
# from __future__ import annotations, where accessing the __annotations__
# attribute directly might return annotations for the wrong class.
if isinstance(obj, type):
try:
ann = _BASE_GET_ANNOTATIONS(obj)
except AttributeError:
# For static types, the descriptor raises AttributeError.
return None
else:
ann = getattr(obj, "__annotations__", None)
if ann is None:
return None
if not isinstance(ann, dict):
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
return ann
class _ExtraNameFixer(ast.NodeTransformer):
"""Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
def __init__(self, extra_names):
self.extra_names = extra_names
def visit_Name(self, node: ast.Name):
if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
node = ast.Name(id=type_repr(new_name))
return node
View on GitHub (pinned to bc6749cc3b)
Solutions
- Set __annotations__ to a plain dict: obj.__annotations__ = {'x': int}.
- Delete the bogus attribute (del obj.__annotations__) so the lookup returns None and get_annotations falls back to __annotate__ or {}.
- Validate with isinstance(obj.__annotations__, (dict, type(None))) before calling annotation APIs.
Example fix
# before
class C: pass
C.__annotations__ = ['x', 'y'] # later: ValueError: ...__annotations__ is neither a dict nor None
# after
C.__annotations__ = {'x': int, 'y': str} Defensive patterns
Strategy: validation
Validate before calling
def annotations_ok(obj) -> bool:
ann = getattr(obj, '__annotations__', None)
return ann is None or isinstance(ann, dict) Type guard
def has_dict_annotations(obj) -> bool:
return isinstance(getattr(obj, '__annotations__', None), dict) Try / catch
try:
ann = get_annotations(obj)
except ValueError as e:
if 'neither a dict nor None' in str(e):
object.__setattr__(obj, '__annotations__', {}) # repair then retry
ann = get_annotations(obj)
else:
raise Prevention
- Only ever assign dicts (or None) to __annotations__
- Validate dynamically injected __annotations__ with isinstance checks
- After unpickling/serialization, re-normalize __annotations__ before introspection
When it happens
Trigger: instance.__annotations__ = 'x: int'; a class body assigning __annotations__ = [] ; a mock or dataclass-like tool setting __annotations__ to a non-dict; a property named __annotations__ returning a tuple.
Common situations: Dynamic annotation injection by ORMs/serializers that writes the wrong type; patching in tests; pickling/copying artifacts that restore __annotations__ as another structure; typos assigning a tuple of keys.
Related errors
- {obj!r} does not have annotations
- Invalid format: {format!r}
- Unsupported format {format!r}
- negative truncate position %r
- invalid number of bytes to read
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/776c874b06638238.
Report an issue: GitHub.