python/cpython · error · AttributeError

module 'ast' has no attribute {attr!r}

Error message

module 'ast' has no attribute {attr!r}

What it means

ast's module-level __getattr__ serves the deprecated node classes removed from the main namespace (Index, ExtSlice, Suite, AugLoad, AugStore, Param, and peers kept in _deprecated). A lookup that misses _deprecated entirely raises AttributeError; names present in the mapping emit a DeprecationWarning (removal targeted for 3.21) and return the shim class. This pattern replaced the old hard ImportError after these classes were made subclasses of their replacements.

Source

Thrown at Lib/ast.py:733

    print(dump(tree, include_attributes=args.include_attributes,
               color=can_colorize(file=sys.stdout),
               indent=args.indent, show_empty=args.show_empty))

_deprecated = {
        'slice': globals().pop("slice"),
        'Index': globals().pop("Index"),
        'ExtSlice': globals().pop("ExtSlice"),
        'Suite': globals().pop("Suite"),
        'AugLoad': globals().pop("AugLoad"),
        'AugStore': globals().pop("AugStore"),
        'Param': globals().pop("Param")
}

def __getattr__(attr):
    try:
        val = _deprecated[attr]
    except KeyError:
        raise AttributeError(f"module 'ast' has no attribute {attr!r}") from None
    warnings._deprecated(f"ast.{attr}", remove=(3, 21))
    return val

if __name__ == '__main__':
    main()

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Replace deprecated classes: use the wrapped node directly (e.g. x instead of ast.Index(x) for subscripts; ast.Tuple for ExtSlice; plain Load/Store for AugLoad/AugStore; drop ast.Param in favor of plain arg).
  2. Pin or upgrade the tooling — many codemod libraries (e.g. libcst, astroid) already shipped 3.9+ fixes.
  3. For dynamic lookups use getattr(ast, name, None) and check sys.version_info before touching removed names.

Example fix

# before
slc = ast.Index(value=ast.Name(id='i'))  # deprecation + removal in 3.21

# after
slc = ast.Name(id='i', ctx=ast.Load())  # bare expression as index (3.9+)
Defensive patterns

Strategy: type-guard

Validate before calling

DEPRECATED_AST = {'Index', 'ExtSlice', 'Suite', 'AugLoad', 'AugStore', 'Param'}

def ast_attr(name, default=None):
    if name in DEPRECATED_AST:
        return default  # do not touch the deprecation shims
    return getattr(ast, name, default)

Type guard

def ast_has(name) -> bool:
    """True only for non-deprecated attributes."""
    import warnings
    DEPRECATED = {'Index', 'ExtSlice', 'Suite', 'AugLoad', 'AugStore', 'Param'}
    return name not in DEPRECATED and hasattr(ast, name)

Try / catch

try:
    cls = getattr(ast, name)
except AttributeError:
    raise RuntimeError(
        f'ast.{name} does not exist on Python {sys.version.split()[0]}; '
        f'use 3.9+ node classes') from None

Prevention

When it happens

Trigger: ast.Index / ast.ExtSlice / ast.Suite / ast.AugLoad / ast.AugStore / ast.Param accessed directly (deprecation path); any other misspelled or nonexistent ast name such as ast.AstParser raising the AttributeError; getattr(ast, name) probes in version-adaptive code.

Common situations: Old AST-rewriting tools (codemods, macros, optimizers) written before 3.9 when Index/SExtSlice were real node classes; code that constructs ast.Index(value) while targeting multiple Python versions; hasattr-based feature detection against ast.

Related errors


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