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

  1. Ensure the input contains only literals: numbers, strings, bytes, booleans, None, tuples, lists, dicts with literal keys and values.
  2. Replace non-literal syntax (sets -> lists, ternaries -> resolved values) at the producer side.
  3. 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

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

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/7761e90e0f357986. Report an issue: GitHub.