1

I've found that setting integer variables all at once works as expected:

foo = bar = cue = 0
foo += 1
bar += 2
print(foo, bar, cue)

1 2 0

However, when doing the same thing with a list type, it updates all of the vars at the same time. The resulting vars will always equal one another.

foo = bar = cue = []
foo += [1]
bar += [2]
print(foo, bar, cue)


[1, 2] [1, 2] [1, 2]

Why does the behavior differ depending on the variable type? Thanks much.

Rino Bino
  • 366
  • 3
  • 15
  • 1
    In the first example, you are assigning entirely new values to `foo` and `bar`; the previous value they held is completely irrelevant. In the second example, you are mutating the existing value of the variables - and since they all refer to the same object, they all see the changes. – jasonharper Aug 13 '22 at 00:53
  • 1
    There's no difference in the initial binding/assignment. In both cases, all variables will be pointing to the same underlying object, whether its an integer or a list. The difference is that integers are immutable, whereas lists are mutable. The `+=` operation performs the `__iadd__` method, which on lists is an in-place operation (it mutates the object), whereas for integers it will rebind the variable to a different integer object (equivalent to `foo = foo + 1`) – Paul M. Aug 13 '22 at 00:53
  • 3
    The short explanation is that `+=` works differently for integers and lists. – John Gordon Aug 13 '22 at 00:54
  • They are all the same list, and the addition extends the list, it does not perform addition on the elements in the list. – Grismar Aug 13 '22 at 00:59

1 Answers1

3

The augmented assignment operator += hooks into the datamodel magic method __iadd__ ("in-place addition"). If this method doesn't exist, it falls back to using the regular __add__ method. From the docs:

These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). If a specific method is not defined, the augmented assignment falls back to the normal methods.

It happens that int doesn't define int.__iadd__, because it does not make sense to do anything "in-place" on an immutable type.

>>> int.__iadd__
AttributeError: type object 'int' has no attribute '__iadd__'

However, list does implement it, and "extends" the existing list with elements taken from iterating the right hand side:

>>> list.__iadd__
<slot wrapper '__iadd__' of 'list' objects>

So when foo == 0, the augmented assignment foo += 1 does something like this:

foo = foo.__add__(1)  # returns a different integer instance

Whereas when foo == [] the augmented assigmnent foo += [1] does something more like this:

foo = foo.__iadd__([1])  # modifies foo in-place and returns foo again

It follows that foo, bar, and cue are names bound to different integer objects in the first case, but bound to the same list instance in the second case.

wim
  • 338,267
  • 99
  • 616
  • 750