python/cpython · error · ValueError
malformed node or string: {node!r}
Error message
malformed node or string: {node!r} What it means
literal_eval walks the AST produced from the string/node and accepts only literal constructs (constants, and +,-,~ on numbers, and complex real+imag additions). Any other node type — names, calls, comprehensions, comparisons, non-numeric BinOps — falls through to the final raise: ValueError('malformed node or string[: on line N]: <node repr>'). It is a hard rejection of anything that is not a Python literal, including seemingly innocent expressions.
Source
Thrown at Lib/ast.py:114
return + operand
else:
return - operand
if (
isinstance(node, BinOp)
and isinstance(node.op, (Add, Sub))
and isinstance(node.left, (Constant, UnaryOp))
and isinstance(node.right, Constant)
and type(left := _convert_literal(node.left)) in (int, float)
and type(right := _convert_literal(node.right)) is complex
):
if isinstance(node.op, Add):
return left + right
else:
return left - right
msg = "malformed node or string"
if lno := getattr(node, 'lineno', None):
msg += f' on line {lno}'
raise ValueError(msg + f': {node!r}')
def dump(
node, annotate_fields=True, include_attributes=False,
*,
color=False, indent=None, show_empty=False,
):
"""
Return a formatted dump of the tree in node. This is mainly useful for
debugging purposes.
If annotate_fields is true (by default), the returned string will show the
names and the values for fields. If annotate_fields is false, the result
string will be more compact by omitting unambiguous field names.
Attributes such as line numbers and column offsets are not dumped by default.
If this is wanted, include_attributes can be set to true.
View on GitHub (pinned to bc6749cc3b)
Solutions
- Ensure the input contains only literals: numbers, strings, bytes, booleans, None, tuples, lists, dicts with literal keys and values.
- Replace non-literal syntax (sets -> lists, ternaries -> resolved values) at the producer side.
- If the data is actually JSON, use json.loads; if you truly need expressions, that is what eval does — do it only on fully trusted input.
Example fix
# before
value = ast.literal_eval("{'a': {1, 2}}") # set literal -> malformed node or string
# after
value = ast.literal_eval("{'a': [1, 2]}") # list literal is accepted Defensive patterns
Strategy: try-catch
Validate before calling
import re
LITERAL_OK = re.compile(
r'^[\s\d\.eEjJ+-()\[\]{},:\'"_abfnrtxuU]*$') # cheap smoke test only
def probably_literal(s):
return LITERAL_OK.match(s) is not None Type guard
def is_literal_string(s):
try:
ast.literal_eval(s)
except (ValueError, SyntaxError):
return False
return True Try / catch
try:
value = ast.literal_eval(raw)
except ValueError as e:
if 'malformed node or string' in str(e):
raise ConfigError(f'non-literal data in config: {raw[:80]!r}') from None Prevention
- Write configs with repr() of pure literals only, or switch to json.dumps/json.loads.
- Reject sets, ternaries, names and calls at the producer side — literal_eval will not accept them.
- Treat literal_eval failures as untrusted input, never fall back to eval().
When it happens
Trigger: ast.literal_eval('1 if x else 2') (ternary/name); literal_eval("{'a': {1, 2}}") — sets are not supported; literal_eval('f"{x}"'); literal_eval('1 < 2') (Compare node); passing a random AST node object such as a Module where an expression node is expected. Note +1j and 'a'+'b' style numeric/string constant folding beyond the numeric cases is also rejected.
Common situations: Parsing JSON-ish config files that contain tuples/single quotes (literal_eval used instead of json.loads); safely evaluating 'trusted-shaped' data from environment variables or saved state that later grows non-literal syntax; deserializing data written by repr() of objects that are not pure literals.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unsupported major version: {major}
- Node can't use cause without an exception.
- Unexpected node inside JoinedStr, {node!r}
- Unknown timespec value
- Invalid ISO string
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/7761e90e0f357986.
Report an issue: GitHub.