Skip to content
Advertisement

How did print(*a, a.pop(0)) change?

This code:

a = [1, 2, 3]
print(*a, a.pop(0))

Python 3.8 prints 2 3 1 (does the pop before unpacking).
Python 3.9 prints 1 2 3 1 (does the pop after unpacking).

What caused the change? I didn’t find it in the changelog.

Edit: Not just in function calls but also for example in a list display:

a = [1, 2, 3]
b = [*a, a.pop(0)]
print(b)

Prints [2, 3, 1] vs [1, 2, 3, 1]. And Expression lists says “The expressions are evaluated from left to right” (that’s the link to Python 3.8 documentation), so I’d expect the unpacking expression to happen first.

Advertisement

Answer

I suspect this may have been an accident, though I prefer the new behavior.

The new behavior is a consequence of a change to how the bytecode for * arguments works. The change is in the changelog under Python 3.9.0 alpha 3:

bpo-39320: Replace four complex bytecodes for building sequences with three simpler ones.

The following four bytecodes have been removed:

  • BUILD_LIST_UNPACK
  • BUILD_TUPLE_UNPACK
  • BUILD_SET_UNPACK
  • BUILD_TUPLE_UNPACK_WITH_CALL

The following three bytecodes have been added:

  • LIST_TO_TUPLE
  • LIST_EXTEND
  • SET_UPDATE

On Python 3.8, the bytecode for f(*a, a.pop()) looks like this:

  1           0 LOAD_NAME                0 (f)
              2 LOAD_NAME                1 (a)
              4 LOAD_NAME                1 (a)
              6 LOAD_METHOD              2 (pop)
              8 CALL_METHOD              0
             10 BUILD_TUPLE              1
             12 BUILD_TUPLE_UNPACK_WITH_CALL     2
             14 CALL_FUNCTION_EX         0
             16 RETURN_VALUE

while on 3.9, it looks like this:

  1           0 LOAD_NAME                0 (f)
              2 BUILD_LIST               0
              4 LOAD_NAME                1 (a)
              6 LIST_EXTEND              1
              8 LOAD_NAME                1 (a)
             10 LOAD_METHOD              2 (pop)
             12 CALL_METHOD              0
             14 LIST_APPEND              1
             16 LIST_TO_TUPLE
             18 CALL_FUNCTION_EX         0
             20 RETURN_VALUE

In the old bytecode, the code pushes a and (a.pop(),) onto the stack, then unpacks those two iterables into a tuple. In the new bytecode, the code pushes a list onto the stack, then does l.extend(a) and l.append(a.pop()), then calls tuple(l).

This change has the effect of shifting the unpacking of a to before the pop call, but this doesn’t seem to have been deliberate. Looking at bpo-39320, the intent was to simplify the bytecode instructions, not to change the behavior, and the bpo thread has no discussion of behavior changes.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement