Behavior of += (was Re: [Python-Dev] Customization docs)

Bengt Richter bokr at oz.net
Wed Jun 5 20:20:43 EDT 2002


On 5 Jun 2002 00:06:22 GMT, "Donn Cave" <donn at u.washington.edu> wrote:

>Quoth bokr at oz.net (Bengt Richter):
>...
>| Here is a contrived case where += lets you use a
>| mutable-lhs-target-expression-with-side-effect and
>| get only one side effect. There are obviously other
>| ways, but at least += is convenient (and presumably
>| faster in a case like this).
>|
>|  >>> a=[0]
>|  >>> def foo():
>|  ...     print 'side effect'
>|  ...     return a
>|  ...
>|  >>> foo()
>|  side effect
>|  [0]
>|  >>> foo()[0]+=1
>|  side effect
>|  >>> a
>|  [1]
>|  >>> foo()[0] = foo()[0]+1
>|  side effect
>|  side effect
>|  >>> a
>|  [2]
>
>I imagine you have some actual application for something like this,
>but the example doesn't seem very compelling - like you say, there
>are obviously other ways.  If they're less convenient, maybe they're
>easier on the eyes.
>
>But it's probably a moot point.  Whether the current behavior
>of += is a wart or not, it's not likely to be removed.
>
Sometimes a <op>= b is explained as though it were a kind of textual abbreviation
for a = a <op> b. You could spec the semantics by saying it should work "as if" that
were true, but that is not true for Python (and I wouldn't like it if it were).

As it says in

    http://www.python.org/doc/ref/augassign.html
 
"An augmented assignment expression like x += 1 can be rewritten as x = x + 1
                         ^^^^^^^^^^-- statement??, BTW ;-)
to achieve a similar, but not exactly equal effect. In the augmented version,
x is only evaluated once. Also, when possible, the actual operation is performed
in-place, meaning that rather than creating a new object and assigning that to
the target, the old object is modified instead."

("Evaluated once" was my main point, which matters most if there are side effects).

I'm not sure how best to translate "assignment" talk to "binding" talk here.
Perhaps we could speak of "composition-bindings" when speaking of the way
composite objects are created or modified to "contain" other objects via
object->object associations, and "name-bindings" for the usual name->object
associations. However, when I think about it, ISTM _all_ bindings are really
ultimately composition-bindings, and a name is just one way of identifying
the origin in one object of a particular binding-arc going to another.

Even ordinary assignment can't guarantee rebinding of a name, since a name
can identify a property or specially managed attribute, etc., so we can't
well expect augmented assignment to be any less complex in its semantics.

<aside>
I would assume you could have a list of properties with no individual names,
but I haven't tried it. If everything works as it should, it should be possible
to write

    property_list[i] += 1

and have it do the right thing. Or are property objects dependent on access
via subversion of attribute name access? (Suspecting so).
</aside>

Anyway, the main subject was += wrt immutables. The key seems to be not whether
a binding is to an immutable target, but whether the binding _itself_ is immutable,
as in an ordinary tuple's bindings of its 'contained' objects.

An ordinary name binding is _itself_ not ordinarily immutable, since we can usually
rebind. I.e., names usually identify binding sites in a mutable name space.

Ordinarily a name doesn't identify an immutable binding-site, so rebinding is not
a problem. The binding sites in a tuple are immutable, so rebinding can't be done
without violating the meaning of whatver is binding to the tuple as such.

However, the components of a tuple (i.e., the objects bound to its binding sites)
can be mutable, and they can be mutated without rebinding. Hence the strange-looking
(at first) value results of Jeff's original post, which our forgiving tuple class
duplicates without complaining:

 >>> t = ([1], [2], [3])
 >>> t[1] += [3]
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: object doesn't support item assignment
 >>> t
 ([1], [2, 3], [3])
 >>>

But:

 >>> t2 = T( ([1], [2], [3]) )
 >>> t2
 ([1], [2], [3])
 >>> t2[1] += [3]
 >>> t2
 ([1], [2, 3], [3])

Regards,
Bengt Richter



More information about the Python-list mailing list