Augument assignment versus regular assignment

Paul Boddie paul at boddie.org.uk
Tue Jul 18 06:48:51 EDT 2006


Antoon Pardon wrote:
>
> Now maybe I'm just not bright enough, so maybe you can explain what
> something like col['t'] is evaluated to in a statement like:
>
>   col['t'] = exp

In the notation given earlier, let us say that it would be this:

namespace = col
setitem(namespace, "t", exp)

Note that in my notation I use the term namespace to apply to actual
namespaces (locals, globals, classes, objects) as well as what I call
"itemspaces" (things which store items). In the above, we still obtain
the namespace first, but we don't attempt to fetch the item previously
stored at item "t" (if any was present).

[...]

> Well maybe I'm missing your point entirely but would you be so friendly,
> as to rephrase the following statements too:
>
>   a = a + b

This is where it gets interesting, even though it should not appear to
do so. We might consider writing this as follows:

setname(ns_target, "a", add(getname(ns_source, "a"), b))

Here, I've skipped over how b is obtained, but it'd be something like
getname(locals, "b") or getname(globals, "b"). Since recent versions of
Python perform some special tricks to disallow assignments involving
mixed namespaces (considering a is global and that the above code is in
a function), we probably can assume the same namespace is at work. But
really, the most pertinent question relates to the evaluation of the
namespaces themselves: a simple name binding operation involves the
local or global namespaces which can be obtained without side-effects.
Thus, the simple assignment above is mostly equivalent to the augmented
assignment...

a += b

...since ns_target == ns_source and their evaluation can be considered
free of side-effects. In other words, we can rewrite it thus:

assert ns_target == ns_source
namespace = ns_target
setname(namespace, "a", add(getname(namespace, "a"), b))

>   a.b = a.b + c

We could consider writing this:

setattr(a, "b", add(getattr(a, "b"), c))

Once again, I've skipped over how c is obtained, since it isn't central
to the assignment. As above, it appears that the namespace is the same
on both sides of the assignment (given Python's assignment rules),
although by using an opaque identifier the above example doesn't really
permit us to illustrate this. We could expand the identifier a to
investigate further:

setattr(getname(ns_target, "a"), "b", add(getattr(getname(ns_source,
"a"), "b"), c))

Which according to the above reduces to this:

setattr(getname(namespace, "a"), "b", add(getattr(getname(namespace,
"a"), "b"), c))

And to this:

ns_a = getname(namespace, "a")
setattr(ns_a, "b", add(getattr(ns_a, "b"), c))

Which shows that the augmented assignment is probably equivalent. See
below for a better example.

>   a[b] = a[b] + c

And again, we could consider writing this:

setitem(a, b, add(getitem(a, b), c))

Once again, I've skipped over how b and c are obtained. Consider this
as an exercise! ;-) And as noted above, the example doesn't really
permit us to illustrate this point - you could expand the terms and
produce something that suggests that this simple case is equivalent to
an augmented assignment.

> Because as far as I can see the rephrasing of these statements will
> result in exactly the same results as their augmented assignment
> counter parts. So following your logic above, we would have to
> come to the conclusion that the "targets" of these statements are
> evaluated once too. This while the language reference suggests
> they are evaluated twice.

Quoting from the reference: "In the augmented version, x is only
evaluated once." Let us look in depth at some of the unillustrated
issues from above.

> IMO the language reference sugest that the stament:
>
>   a[i] += [b]

Here, we can be sure that the same namespace is involved:

namespace = a
setitem(namespace, i, add(getitem(namespace, i), [b]))

> is more effective than:
>
>   a[i] = a[i] + [b]

Whilst I, knowing that a appears twice and believing that a always
produces the same result, might write the same as above, let us
consider a more complicated expression instead of a (where the function
f returns a list):

f()[i] = f()[i] + [b]

Here, since we can make no guarantees that f() evaluates to the same
thing every time it is called, we must evaluate it twice to honour the
intent of the statement:

setitem(f(), i, add(getitem(f(), i), [b]

To see this clearly...

f1 = f()
f2 = f()
setitem(f1, i, add(getitem(f2, i), [b])

Meanwhile, in the case of the augmented assignment...

f()[i] += [b]

...the author has more or less stated that he/she wants f() to be
evaluated only once - anything else would be surprising:

namespace = f()
setitem(namespace, i, add(getitem(namespace, i), b))

> IMO it suggests that the augmented assignment version is
> just as effective as:
>
>   a[i].append(b)
>
> Because, i hope, we agree that a[i] is evaluated only
> once here!

The rule is that if the namespace (what you call the primary, I think)
is mentioned once, it is evaluated only once.

[...]

> Yes, and what you are doing here is eliminating the double evaluation
> of the primary. In a statement like a.f().x = ... the a.f() part
> is the primary according to the assignment reference.

Indeed. The language reference uses very simple opaque identifiers to
supposedly illustrate its point but, as we saw above, such examples
actually obstruct the path to understanding. That a simple identifier x
obtained from some namespace is not evaluated twice is almost
tangential to understanding the principles involved.

Paul




More information about the Python-list mailing list