`ast.Constant.value` `tuple`s and `frozenset`s

Why are tuples and frozensets in an ast.Constant.value noted in the docs and supported by ast.unparse, while they aren’t outputted from ast.parse? Does it output it in a specific version?

Consider this function:

def is_any(x):
    return x in (1, 4, 2, 8, 5, 7)

Does the AST for this function include a constant tuple? Let’s see.

>>> ast.dump(ast.parse("lambda x: x in (1, 4, 2, 8, 5, 7)"))
"Module(body=[Expr(value=Lambda(args=arguments(posonlyargs=[], args=[arg(arg='x')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=Compare(left=Name(id='x', ctx=Load()), ops=[In()], comparators=[Tuple(elts=[Constant(value=1), Constant(value=4), Constant(value=2), Constant(value=8), Constant(value=5), Constant(value=7)], ctx=Load())])))], type_ignores=[])"

Nope, just the Tuple syntax. But what if we disassemble the function itself?

>>> dis.dis(is_any)
  1           0 RESUME                   0

  2           2 LOAD_FAST                0 (x)
              4 LOAD_CONST               1 ((1, 4, 2, 8, 5, 7))
              6 CONTAINS_OP              0
              8 RETURN_VALUE

Ah! Now it’s loading a constant tuple.

Tuples and frozensets can be constants stored on a function object, but only after the peephole optimizer has done its work. So at the time of parsing to AST, you’ll never see them; but if you’re decompiling a function object and trying to reconstitute its source code, there’s every chance you’ll get the optimized version. As such, it’s handy for unparse to be able to show you what would give that result.

So, it’s only there to support bytecode disassembly? That’s not a built-in feature right?
Does this mean that I can always assume ast.parse doesn’t do those optimizations?

Only? Not sure. There might be other uses, but that’s the first one that came to mind. But yes, you should be able to assume that AST parsing just parses and doesn’t optimize. For example:

>>> ast.dump(ast.parse("3+4j"))
'Module(body=[Expr(value=BinOp(left=Constant(value=3), op=Add(), right=Constant(value=4j)))], type_ignores=[])'

even though (3+4j) behaves broadly like a constant (and is subject to constant folding).

Alright, thanks!