dis code of += to INPLACE_ADD - explain why it works in this way

Viewed 74

I have very simple function f, using dis.dis I disassemble it:

def f():
  a = "xxx"
  print(id(a))
  a = a + "A"
  print(id(a))
  a = a + "B"
  print(id(a))
f()

from dis import dis
print(dis(f))

From https://pd.codechef.com/docs/py/2.7.9/library/dis.html I know that:

INPLACE_ADD(): Implements in-place TOS = TOS1 + TOS.

the output with my questions-comments is:

140387882962224
140387687265136
140387687265136
  2           0 LOAD_CONST               1 ('xxx')
              2 STORE_FAST               0 (a)

  3           4 LOAD_GLOBAL              0 (print) # id(a)->140387882962224
              6 LOAD_GLOBAL              1 (id)
              8 LOAD_FAST                0 (a)
             10 CALL_FUNCTION            1
             12 CALL_FUNCTION            1
             14 POP_TOP

  4          16 LOAD_FAST                0 (a)     # id(a) is 140387882962224, TOS = 'xxx'
             18 LOAD_CONST               2 ('A')   # TOS = 'A', TOS1 = 'xxx'
             20 BINARY_ADD                         # TOS = TOS1+TOS = 'xxx'+'A' = 'xxxA'
             22 STORE_FAST               0 (a)     # id(a) is 140387882962224?

  5          24 LOAD_GLOBAL              0 (print) id(a)->140387687265136
             26 LOAD_GLOBAL              1 (id)
             28 LOAD_FAST                0 (a)
             30 CALL_FUNCTION            1
             32 CALL_FUNCTION            1
             34 POP_TOP

  6          36 LOAD_FAST                0 (a)
             38 LOAD_CONST               3 ('B')
             40 BINARY_ADD
             42 STORE_FAST               0 (a)

  7          44 LOAD_GLOBAL              0 (print)
             46 LOAD_GLOBAL              1 (id)
             48 LOAD_FAST                0 (a)
             50 CALL_FUNCTION            1
             52 CALL_FUNCTION            1
             54 POP_TOP
             56 LOAD_CONST               0 (None)
             58 RETURN_VALUE

Where can I see the change of id of the a in bytecode?

1 Answers

BINARY_ADD pushes a new object on the stack. That new object has a new ID. No existing object ever changes ID. Strings and integers are immutable; they are not changed in-place.

This is actually a key part of understanding Python: the separation between names and objects. Names are just a convenience for the programmer -- note how there are no names in the disassembly at all. Names are bound to objects, all of which are anonymous and live in an "object cloud". a starts off bound to the anonymous string object "xxx". After your first change, a is now bound to a new string object with "xxxA"; the string object "xxx" is no longer needed and will be reclaimed.

Related