a,b = 2,3 and [a,b] = [2,3]

Chris Angelico rosuav at gmail.com
Mon Sep 2 05:24:15 EDT 2019


On Mon, Sep 2, 2019 at 6:31 PM Alan Bawden <alan at csail.mit.edu> wrote:
>
> Dang!  There is exactly the instruction sequence I just argued could be
> optimized away still sitting right there.  So maybe my belief that this is
> being done by peephole optimization is in fact incorrect?  So I went and
> tried again:
>
> bash-4.2$ python3 -E
> Python 3.6.6 (default, Aug 13 2018, 18:24:23)
> [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
> Type "help", "copyright", "credits" or "license" for more information.
> >>> def f():
> ...     a, b = 2, 3
> ...     a, b = [2, 3]
> ...     a, b = b, a
> ...     a, b = [b, a]
> ...
> >>> import dis
> >>> dis.dis(f)
>   2           0 LOAD_CONST               3 ((2, 3))
>               2 UNPACK_SEQUENCE          2
>               4 STORE_FAST               0 (a)
>               6 STORE_FAST               1 (b)
>
>   3           8 LOAD_CONST               1 (2)
>              10 LOAD_CONST               2 (3)
>              12 ROT_TWO
>              14 STORE_FAST               0 (a)
>              16 STORE_FAST               1 (b)
>
>   4          18 LOAD_FAST                1 (b)
>              20 LOAD_FAST                0 (a)
>              22 ROT_TWO
>              24 STORE_FAST               0 (a)
>              26 STORE_FAST               1 (b)
>
>   5          28 LOAD_FAST                1 (b)
>              30 LOAD_FAST                0 (a)
>              32 ROT_TWO
>              34 STORE_FAST               0 (a)
>              36 STORE_FAST               1 (b)
>              38 LOAD_CONST               0 (None)
>              40 RETURN_VALUE
>
> OK, now I'm confused.  How come I'm not seeing the
> BUILD_LIST/UNPACK_SEQUENCE sequence that you're seeing?
>
> > This is with CPython 3.9. It's entirely possible that other Pythons
> > and/or other versions of CPython may give different results, but with
> > this particular interpreter, the list is not optimized away.
>
> I actually did try this with several different versions of CPython going
> back to 2.4 and up to 3.6, and they all behave this way for me.  Maybe
> something changed after 3.6?  Weird...

This is indeed fascinating. Something DID indeed change. I tried this
slightly shorter version in a few different Pythons:

def f():
    a, b = b, a
    a, b = [b, a]
import dis, sys
print(sys.version)
dis.dis(f)
# Show the code identically on 2.x and 3.x
print(repr(f.__code__.co_code).lstrip("b"))

Here's what I learned:

CPython 2.7: ROT_TWO, bytecode
CPython 3.4: ROT_TWO, bytecode
CPython 3.5: ROT_TWO, bytecode
CPython 3.6: ROT_TWO, wordcode
CPython 3.7: UNPACK_SEQUENCE
CPython 3.8: UNPACK_SEQUENCE
CPython 3.9: UNPACK_SEQUENCE
PyPy 5.6 (2.7): out-of-order LOAD/STORE
PyPyJS (2.7.9): out-of-order LOAD/STORE
Jython 2.5.3: unable to disassemble
MicroPython 3.4: no 'dis' module or __code__ attr
Brython: unable to disassemble

CPython 3.6 made the change to wordcode. If you have a 3.5 hanging
around, you should be able to see this easily in the disassembly,
because the LOAD_FAST operations require three bytes each in 3.5, but
only one (two byte) operation in 3.6, but the ROT_TWO is a single byte
in 3.5 and now requires two in 3.6. PyPy can reorder operations
knowing that it won't affect anything, and thus optimizes it down to
"load b, load a, store b, store a" regardless of the syntax.

But the curious difference happens in 3.7. I don't know what changed
to cause this, but from there on, the list gets built and then
unpacked.

This may represent a performance regression. Alternatively, just take
it as a recommendation to always do your variable exchanges WITHOUT
square brackets, and you'll be fine.

ChrisA



More information about the Python-list mailing list