git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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


Chris Angelico <rosuav at gmail.com> writes:

> On Mon, Sep 2, 2019 at 12:36 PM Alan Bawden <alan at csail.mit.edu> wrote:
...
> > > > a,b = 2,3 and [a,b] = [2,3]
...
> > It looks to me like they generate identical code.  The first one calls the
> > construction of a tuple, where the second one calls for the construction of
> > a list.  It would be surprising if the compiler optimized the tuple
> > away, but failed to optimize the list away!
> >
> 
> Well, you can find out with the 'dis' module.

Actually, when I wrote "It looks to me", I was in fact looking at the
results of calling `dis.dis'!  But I had unconciously changed what the OP
wrote to make it more realistic.  I tested:

>>> def f(x, y):
      x, y = y, x
      [x, y] = [y, x]
>>> dis.dis(f)
  2           0 LOAD_FAST                1 (y)
              3 LOAD_FAST                0 (x)
              6 ROT_TWO
              7 STORE_FAST               0 (x)
             10 STORE_FAST               1 (y)

  3          13 LOAD_FAST                1 (y)
             16 LOAD_FAST                0 (x)
             19 ROT_TWO
             20 STORE_FAST               0 (x)
             23 STORE_FAST               1 (y)
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

And I observed that whatever peephole optimization got rid of the tuple
also got rid of the list.  Clearly "BUILD_LIST 2" or "BUILD_TUPLE 2"
followed by "UNPACK_SEQUENCE 2" can be replaced by "ROT_TWO", and the
compiler knew that!  It never occured to me that the case where all the
elements of the sequence to the right of the "=" were constants would
change that, but yes, you are right about that (but read on...):

> >>> def f():
> ...     a, b = 2, 3
> ...     a, b = [2, 3]
> ...
> >>> dis.dis(f)
>   2           0 LOAD_CONST               1 ((2, 3))
>               2 UNPACK_SEQUENCE          2
>               4 STORE_FAST               0 (a)
>               6 STORE_FAST               1 (b)

And indeed, if they are all constants, then the tuple itself is a constant,
and the obvious peephole optimization is no longer available!

But wait...

>   3           8 LOAD_CONST               2 (2)
>              10 LOAD_CONST               3 (3)
>              12 BUILD_LIST               2
>              14 UNPACK_SEQUENCE          2
>              16 STORE_FAST               0 (a)
>              18 STORE_FAST               1 (b)

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...

-- 
Alan Bawden