Hidden comprehension variables and `dis.show_code`

While reviewing gh-120906: Support arbitrary hashable keys in FrameLocalsProxy by encukou · Pull Request #122309 · python/cpython · GitHub I noticed that dis.show_code doesn’t clearly distinguish the hidden local variables added for inlined module level comprehensions from the regular local variables.

>>> dis.show_code("[x for x in range(10)]")
Name:              <module>
Filename:          <disassembly>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             0x0
Constants:
   0: 10
Names:
   0: range
Variable names:
   0: x
>>> def f():
...     return [x for x in range(10)]
...
>>> dis.show_code(f)
Name:              f
Filename:          <python-input-13>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             OPTIMIZED, NEWLOCALS
Constants:
   0: None
   1: 10
Names:
   0: range
Variable names:
   0: x

The fact x is a hidden iteration variable in the first case is kinda implied by the missing OPTIMIZED and NEWLOCALS flags on the overall code object, but when it comes to other differences like free and cell variables we make the differences much more pronounced:

>>> def outer():
...     x = 1
...     def inner():
...         return x
...     return inner
...
>>> dis.show_code(outer)
Name:              outer
Filename:          <python-input-8>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS
Constants:
   0: None
   1: 1
   2: <code object inner at 0x7ffa31807020, file "<python-input-8>", line 3>
Variable names:
   0: inner
Cell variables:
   0: x
>>> dis.show_code(outer())
Name:              inner
Filename:          <python-input-8>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, NESTED
Constants:
   0: None
Free variables:
   0: x

I’m leaning towards “local variables being defined in a code object that doesn’t otherwise use local variables” being a sufficiently clear indicator in the output, but I’m uncertain enough of that conclusion that it seemed worthwhile to post about it here and see if anyone else thought it might be worth trying to improve the way dis.show_code presents these cases.

I think it could be a nice-to-have, but it isn’t critical.

(And FYI for anyone else who didn’t notice initially like me, some of the code examples scroll to show more.)

Good point, I split the nested function example into pieces so it doesn’t hit the vertical scrolling threshold.