Unexpected result for list operator "+="

Tim Peters tim.one at home.com
Thu Jan 4 03:57:46 EST 2001


[Greg Jorgensen]
> You discovered the documented behavior. Assignment of immutable objects
> (numbers, strings, tuples) essentially makes a copy of the object.

Not so.  Assignment (although it's better to think of it as "binding",
because "assignment" often carries inappropriate baggage from other
languages) never makes a copy of anything.

> Assignment of mutable objects (lists, dictionaries) creates a new
> reference to the original object.

True of all bindings, and without exception.  Note that the argument-passing
mechanism in Python also falls under this rule:  passing an argument merely
binds the formal argument name to the passed object (and so there's never
any copying done at call sites either).  The "import X [as Z]", and "from X
import Y [as Z]", statements are also binding mechanisms, and so also never
make copies.

>>> a = [1, 2, 3]  # list (mutable)
>>> b = a
>>> a is b  # a and b name exactly the same object
1
>>> c = "[1, 2, 3]" # string (immutable)
>>> d = c
>>> c is d  # c and d name exactly the same object
1
>>>

No difference in behavior so far.  The difference is in what happens next:

>>> a += [4]
>>> a is b  # still the smae object
1
>>> c += "4"
>>> c is d  # but these are different now
0
>>>

The meaning of "+=" is decided by the object on the left-hand side.  Lists
*choose* to modify themselves in place in response to +=.  They *can* do so
because they're mutable, but they didn't *have* to do so.  It was an
implementation choice, and there was general agreement that a mutating +=
would be most useful most often for lists.

Strings, however, don't have this choice:  since a string is immutable, it
cannot change itself in place, even if it wanted to.  Strings (along with
all other immutable objects) have a different choice:  in response to +=, a
string can can either return a new object to represent the result, or refuse
to implement += at all.  Again, it was thought more useful more often for
strings to return a new object than to refuse to do anything.

Lists (along with all other mutable objects) *could* have chosen to return a
new object instead of mutating, or could have refused to implement +=.  As a
rule of thumb, objects of mutable types generally do choose to mutate,
though.

Now for a subtlety <wink>:

>>> d
'[1, 2, 3]'
>>> e = d
>>> d is e  # no surprise here:  same object
1
>>> d += ""
>>> d is e  # still the same object!
1
>>>

That shows an optimization strings happen to perform when responding to +=:
if the new value would be identical to the old value, they choose not to
bother creating a new object.  That's also true of regular old string +:

>>> "cat" is "cat" + "" is "" + "cat"
1
>>>

That may or may not remain true across releases, because it's an internal
optimization.  Immutable objects can get away with tricks like this, because
when a name x is bound to an immutable object, nothing except rebinding x
can change the content of the object x is bound to, and for that reason
aliasing has no bad implications (if x and y are bound to the same immutable
object, there is nothing whatsoever you can do to y that will change x's
view of reality, nor can x pull any surprises on y -- without doing id() or
"is", they can't tell whether they're bound to the same object or to
distinct objects that happen to compare ==).

in-some-ways-it's-too-simple-to-grasp-at-first-if-you're-accustomed-to-hairi
er-
    languages-ly y'rs  - tim






More information about the Python-list mailing list