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