python/cpython · error · TypeError
expected AST, got %r
Error message
expected AST, got %r
What it means
ast.dump requires a single AST node instance as its first argument; after its internal _format helper handles lists/None/bools/numbers/strings, the top-level check rejects anything that is not an ast.AST instance with TypeError naming the offending class. Lists of nodes, source strings, tokens, or None passed at top level all fail here.
Source
Thrown at Lib/ast.py:223
args.append(f'{t.attribute}{name}{t.reset}={value}')
cls_name = f'{t.node}{cls.__name__}{t.reset}'
if allsimple and len(args) <= 3:
return f'{cls_name}({", ".join(args)})', not args
return f'{cls_name}({prefix}{sep.join(args)})', False
elif isinstance(node, list):
if not node:
return '[]', True
return '[%s%s]' % (prefix, sep.join(_format(x, level)[0] for x in node)), False
if isinstance(node, bool) or node is None or node is Ellipsis:
return f'{t.keyword}{node!r}{t.reset}', True
if isinstance(node, (int, float, complex)):
return f'{t.number}{node!r}{t.reset}', True
if isinstance(node, (str, bytes)):
return f'{t.string}{node!r}{t.reset}', True
return repr(node), True
if not isinstance(node, AST):
raise TypeError('expected AST, got %r' % node.__class__.__name__)
if indent is not None and not isinstance(indent, str):
indent = ' ' * indent
return _format(node)[0]
def copy_location(new_node, old_node):
"""
Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
attributes) from *old_node* to *new_node* if possible, and return *new_node*.
"""
for attr in 'lineno', 'col_offset', 'end_lineno', 'end_col_offset':
if attr in old_node._attributes and attr in new_node._attributes:
value = getattr(old_node, attr, None)
# end_lineno and end_col_offset are optional attributes, and they
# should be copied whether the value is None or not.
if value is not None or (
hasattr(old_node, attr) and attr.startswith("end_")
):View on GitHub (pinned to bc6749cc3b)
Solutions
- Call ast.parse(source) first and pass its result (a Module node) or an individual statement/expression node.
- For lists of nodes, dump each element: '\n'.join(ast.dump(n) for n in tree.body).
- Type-check before dumping when the node's provenance is dynamic: isinstance(node, ast.AST).
Example fix
# before ast.dump(tree.body) # list of stmts -> TypeError: expected AST, got 'list' # after ast.dump(tree) # whole module # or '\n'.join(ast.dump(n) for n in tree.body)
Defensive patterns
Strategy: type-guard
Validate before calling
if not isinstance(obj, ast.AST):
obj = ast.parse(obj) # accept source strings defensively before dump Type guard
import ast
def is_ast_node(obj) -> bool:
return isinstance(obj, ast.AST)
def dump_any(obj):
if isinstance(obj, ast.AST):
return ast.dump(obj)
if isinstance(obj, (list, tuple)):
return '\n'.join(ast.dump(n) for n in obj if isinstance(n, ast.AST))
raise TypeError(f'cannot dump {type(obj).__name__}') Try / catch
try:
text = ast.dump(node)
except TypeError as e:
if 'expected AST' in str(e):
text = '\n'.join(ast.dump(n) for n in node) # was a list of nodes Prevention
- Always run ast.parse before dump when starting from source text.
- Remember tree.body is a list — index it before dumping individual statements.
- Keep helper functions typed so a list of nodes never reaches ast.dump.
When it happens
Trigger: ast.dump(parse_result.body) where body is a list of statements (must pick one node, e.g. body[0]); ast.dump('x = 1') forgetting ast.parse first; passing the result of ast.walk(...) (a generator); mixing up argument order in helper functions that wrap dump.
Common situations: Debugging helpers that dump 'whatever the traversal returned'; refactors that changed a single-node variable into a list; logging visitor output where lists of nodes are the natural unit.
Related errors
- %r can't have docstrings
- {future!r} object does not appear to be compatible with asyn
- Not implemented
- Unknown thread safety level {level!r} for {name!r}. Valid le
- Node can't use cause without an exception.
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/e3a5d704c0a68286.
Report an issue: GitHub.