1

Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument
List extending strange behaviour
Pyramid traversal view lookup using method names

Let's say I have this function:

def a(b=[]):
    b += [1]
    print b

Calling it yields this result:

>>> a()
[1]
>>> a()
[1, 1]
>>> a()
[1, 1, 1]

When I change b += [1] to b = b + [1], the behavior of the function changes:

>>> a()
[1]
>>> a()
[1]
>>> a()
[1]

How does b = b + [1] differ from b += [1]? Why does this happen?

Community
  • 1
  • 1
Blender
  • 289,723
  • 53
  • 439
  • 496

3 Answers3

5

b += [1] alters the function default (leading to the least astonishment FAQ). b = b + [1] takes the default argument b - creates a new list with the + [1], and binds that to b. One mutates the list - the other creates a new one.

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
5

In Python there is no guarantee that a += b does the same thing as a = a + b.

For lists, someList += otherList modifies someList in place, basically equivalent to someList.extend(otherList), and then rebinds the name someList to that same list. someList = someList + otherList, on the other hand, constructs a new list by concatenating the two lists, and binds the name someList to that new list.

This means that, with +=, the name winds up pointing to the same object it already was pointing to, while with +, it points to a new object. Since function defaults are only evaluated once (see this much-cited question), this means that with += the operations pile up because they all modify the same original object (the default argument).

Community
  • 1
  • 1
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • > and then rebinds the name `someList` to that same list. < I don't think this is true. It doesn't rebind, just changes the list calling its `extend` method. – warvariuc Dec 29 '12 at 19:19
  • 1
    @warwaruk: It is true. You can see it if the assignment is to an attribute (e.g., make an object whose `__setattr__` prints something out, do `obj.someProp=[1]`, then do `obj.someProp += [2]` and watch `__setattr__` get called). See also [here](http://stackoverflow.com/questions/8943613/how-does-the-name-of-an-immutable-object-rebind-to-the-result-of-an-augmented-as). – BrenBarn Dec 29 '12 at 19:22
  • Indeed, `+=` and `+` for lists are two separate implementations: [List extending strange behaviour](http://stackoverflow.com/questions/13904039/13904124#13904124) – Martijn Pieters Dec 29 '12 at 19:38
  • Hm, you are right. I could not understand why, until i figured out, that `obj.a_list += another_list` is equivalent to `obj.a_list = operator.iadd(obj.a_list, another_list)` and not `obj.a_list.extend(another_list)` – warvariuc Dec 29 '12 at 19:39
0

When you define a function

>>> def a(b=[]):
    b += [1]
    return b

it saves all the default arguments in a special place. It can be actually accessed by:

>>> a.func_defaults
([],)

The first default value is a list has the ID:

>>> id(a.func_defaults[0])
15182184

Let's try to invoke the function:

>>> print a()
[1]
>>> print a()
[1, 1]

and see the ID of the value returned:

>>> print id(a())
15182184
>>> print id(a())
15182184

As you may see, it's the same as the ID of the list of the first default value.

The different output of the function is explained by the fact that b+=... modifies the b inplace and doesn't create a new list. And b is the list being kept in the tuple of default values. So all your changes to the list are saved there, and each invocation of the function works with the different value of b.

ovgolovin
  • 13,063
  • 6
  • 47
  • 78