Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > Nested scopes, and augmented assignment

Reply
Thread Tools

Nested scopes, and augmented assignment

 
 
Tim N. van der Leeuw
Guest
Posts: n/a
 
      07-04-2006
Hi,

The following might be documented somewhere, but it hit me unexpectedly
and I couldn't exactly find this in the manual either.

Problem is, that I cannot use augmented assignment operators in a
nested scope, on variables from the outer scope:

PythonWin 2.4.3 (#69, Mar 29 2006, 17:35:34) [MSC v.1310 32 bit
(Intel)] on win32.
Portions Copyright 1994-2004 Mark Hammond ((E-Mail Removed)) -
see 'Help/About PythonWin' for further copyright information.
>>> def foo():

.... def nestedfunc(bar):
.... print bar, ';', localvar
.... localvar += 1
.... localvar=0
.... nestedfunc('bar')
....
>>> foo()

bar =Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 6, in foo
File "<interactive input>", line 3, in nestedfunc
UnboundLocalError: local variable 'localvar' referenced before
assignment
>>>


This is entirely counter-intuitive to me, and searching the manual for
nested scoping rules and for augmented assignment rules, I still feel
that I couldn't have predicted this from the docs.

Is this an implementation artifact, bug, or should it really just
follow logically from the language definition?

Regards,

--Tim

 
Reply With Quote
 
 
 
 
Diez B. Roggisch
Guest
Posts: n/a
 
      07-04-2006
Tim N. van der Leeuw wrote:

> Hi,
>
> The following might be documented somewhere, but it hit me unexpectedly
> and I couldn't exactly find this in the manual either.
>
> Problem is, that I cannot use augmented assignment operators in a
> nested scope, on variables from the outer scope:


<snip/>

> Is this an implementation artifact, bug, or should it really just
> follow logically from the language definition?


From the docs:

"""
An augmented assignment expression like x += 1 can be rewritten as x = x + 1
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.
"""

The first part is the important one. If you expand

x += 1

to

x = x + 1

it becomes clear under the python scoping rules that x is being treated as a
local to the inner scope.

There has been a discussion about this recently on python-dev[1] and this
NG - google for it.



Regards,

Diez


[1] http://mail.python.org/pipermail/pyt...ne/065902.html
 
Reply With Quote
 
 
 
 
Antoon Pardon
Guest
Posts: n/a
 
      07-05-2006
On 2006-07-04, Diez B. Roggisch <(E-Mail Removed)> wrote:
> Tim N. van der Leeuw wrote:
>
>> Hi,
>>
>> The following might be documented somewhere, but it hit me unexpectedly
>> and I couldn't exactly find this in the manual either.
>>
>> Problem is, that I cannot use augmented assignment operators in a
>> nested scope, on variables from the outer scope:

>
><snip/>
>
>> Is this an implementation artifact, bug, or should it really just
>> follow logically from the language definition?

>
> From the docs:
>
> """
> An augmented assignment expression like x += 1 can be rewritten as x = x + 1
> 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.
> """
>
> The first part is the important one. If you expand
>
> x += 1
>
> to
>
> x = x + 1
>
> it becomes clear under the python scoping rules that x is being treated as a
> local to the inner scope.
>
> There has been a discussion about this recently on python-dev[1] and this
> NG - google for it.


Well no matter what explanation you give to it, and I understand how it
works, I keep finding it strange that something like

k = [0]
def f(i):
k[0] += i
f(2)

works but the following doesn't

k = 0
def f(i):
k += i
f(2)


Personnaly I see no reason why finding a name/identifier on the
leftside of an assignment should depend on whether the name
is the target or prefix for the target.


But maybe that is just me.

--
Antoon Pardon
 
Reply With Quote
 
Bruno Desthuilliers
Guest
Posts: n/a
 
      07-05-2006
Antoon Pardon wrote:
(snip)
> Well no matter what explanation you give to it, and I understand how it
> works,


I'm not sure of this.

> I keep finding it strange that something like
>
> k = [0]
> def f(i):
> k[0] += i
> f(2)
>
> works but the following doesn't
>
> k = 0
> def f(i):
> k += i
> f(2)
>
>
> Personnaly I see no reason why finding a name/identifier on the
> leftside of an assignment should depend on whether the name
> is the target or prefix for the target.
>


It's not about "finding a name/identifier", it's about the difference
between (re)binding a name and mutating an object.


--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in '(E-Mail Removed)'.split('@')])"
 
Reply With Quote
 
Fredrik Lundh
Guest
Posts: n/a
 
      07-05-2006
Bruno Desthuilliers wrote:

> It's not about "finding a name/identifier", it's about the difference
> between (re)binding a name and mutating an object.


the difference between binding and performing an operation on an object
(mutating or not), in fact.

this is Python 101.

</F>

 
Reply With Quote
 
Antoon Pardon
Guest
Posts: n/a
 
      07-05-2006
On 2006-07-05, Bruno Desthuilliers <(E-Mail Removed)> wrote:
> Antoon Pardon wrote:
> (snip)
>> Well no matter what explanation you give to it, and I understand how it
>> works,

>
> I'm not sure of this.


Should I care about that?

>> I keep finding it strange that something like
>>
>> k = [0]
>> def f(i):
>> k[0] += i
>> f(2)
>>
>> works but the following doesn't
>>
>> k = 0
>> def f(i):
>> k += i
>> f(2)
>>
>>
>> Personnaly I see no reason why finding a name/identifier on the
>> leftside of an assignment should depend on whether the name
>> is the target or prefix for the target.

>
> It's not about "finding a name/identifier", it's about the difference
> between (re)binding a name and mutating an object.


The two don't contradict each other. Python has chosen that it won't
rebind variables that are out of the local scope. So if the lefthand
side of an assignment is a simple name it will only search in the
local scope for that name. But if the lefthand side is more complicated
if will also search the outerscopes for the name.

Python could have chosen an approach with a "nested" keyword, to allow
rebinding a name in an intermediate scope. It is not that big a deal
that it hasn't, but I keep finding the result strange and somewhat
counterintuitive.

Let me explain why:

Suppose someone is rather new of python and writes the following
code, manipulating vectors:

A = 10 * [0]
def f(B):
...
for i in xrange(10):
A[i] += B[i]
...

Then he hears about the vector and matrix modules that are around.
So he rewrites his code naively as follows:

A = NullVector(10):
def f(B):
...
A += B
...

And it won't work. IMO the principle of least surprise here would
be that it should work.

--
Antoon Pardon
 
Reply With Quote
 
Fredrik Lundh
Guest
Posts: n/a
 
      07-05-2006
Antoon Pardon wrote:

> Python could have chosen an approach with a "nested" keyword


sure, and Python could also have been invented by aliens, powered by
space potatoes, and been illegal to inhale in Belgium.

have any of your "my mental model of how Python works is more important
than how it actually works" ever had a point ?

</F>

 
Reply With Quote
 
Piet van Oostrum
Guest
Posts: n/a
 
      07-05-2006
>>>>> Antoon Pardon <(E-Mail Removed)> (AP) wrote:

>AP> On 2006-07-05, Bruno Desthuilliers <(E-Mail Removed)> wrote:
>>> Antoon Pardon wrote:
>>> (snip)
>>>> Well no matter what explanation you give to it, and I understand how it
>>>> works,
>>>
>>> I'm not sure of this.


>AP> Should I care about that?


Yes, because as long as you don't understand it, you are in for unpleasant
surprises.

>>> It's not about "finding a name/identifier", it's about the difference
>>> between (re)binding a name and mutating an object.


>AP> The two don't contradict each other. Python has chosen that it won't
>AP> rebind variables that are out of the local scope. So if the lefthand
>AP> side of an assignment is a simple name it will only search in the
>AP> local scope for that name. But if the lefthand side is more complicated
>AP> if will also search the outerscopes for the name.


No. It will always use the same search order. But a variable that is bound
inside the function (with an asignment) and is not declared global, is in
the local namespace. A variable that is not assigned to inside the function
is not in the local namespace. An assignment to A[i] is not rebinding A, so
it doesn't count to make the variable A local.

>AP> Python could have chosen an approach with a "nested" keyword, to allow
>AP> rebinding a name in an intermediate scope. It is not that big a deal
>AP> that it hasn't, but I keep finding the result strange and somewhat
>AP> counterintuitive.


Maybe it would have been nice if variables could have been declared as
nested, but I think it shows that nested variables have to be used with
care, similar to globals. Especially not allowing rebinding in intermediate
scopes is a sound principle (`Nested variables considered harmful').
If you need to modify the objects which are bound to names in intermediate
scopes, use methods and give these objects as parameters.

>AP> Let me explain why:


>AP> Suppose someone is rather new of python and writes the following
>AP> code, manipulating vectors:


>AP> A = 10 * [0]
>AP> def f(B):
>AP> ...
>AP> for i in xrange(10):
>AP> A[i] += B[i]
>AP> ...


>AP> Then he hears about the vector and matrix modules that are around.
>AP> So he rewrites his code naively as follows:


>AP> A = NullVector(10):
>AP> def f(B):
>AP> ...
>AP> A += B
>AP> ...


>AP> And it won't work. IMO the principle of least surprise here would
>AP> be that it should work.


Well, A = f(B) introduces a local variable A. Therefore also A = A+B.
And therefore also A += B.

The only way to have no surprises at all would be if local variables would
have to be declared explicitly, like 'local A'. But that would break
existing code. Or maybe the composite assignment operators should have been
exempted. Too late now!

You have the same problem with:

A = [10]
def inner():
A.append(2)

works but

A = [10]
def inner():
A += [2]

doesn't. The nasty thing in this example is that although A += [2] looks
like a rebinding syntactically, semantically there is no rebinding done.

I think the cleaner solution is (use parameters and don't rebind):
def inner(A):
A.append(2)

--
Piet van Oostrum <(E-Mail Removed)>
URL: http://www.cs.uu.nl/~piet [PGP 8DAE142BE17999C4]
Private email: http://www.velocityreviews.com/forums/(E-Mail Removed)
 
Reply With Quote
 
Antoon Pardon
Guest
Posts: n/a
 
      07-06-2006
On 2006-07-05, Fredrik Lundh <(E-Mail Removed)> wrote:
> Antoon Pardon wrote:
>
>> Python could have chosen an approach with a "nested" keyword

>
> sure, and Python could also have been invented by aliens, powered by
> space potatoes, and been illegal to inhale in Belgium.


At one time one could have reacted the same when people suggested
python could use a ternary operator. In the mean time a ternary
operator is in the pipeline. If you don't want to discuss how
python could be different with me, that is fine, but I do no
harm discussing such things with others.

> have any of your "my mental model of how Python works is more important
> than how it actually works" ever had a point ?


Be free to correct me. But just suggesting that I'm wrong doesn't help
me in changing my mental model.

--
Antoon Pardon
 
Reply With Quote
 
Antoon Pardon
Guest
Posts: n/a
 
      07-06-2006
On 2006-07-05, Piet van Oostrum <(E-Mail Removed)> wrote:
>>>>>> Antoon Pardon <(E-Mail Removed)> (AP) wrote:

>
>>AP> On 2006-07-05, Bruno Desthuilliers <(E-Mail Removed)> wrote:
>>>> Antoon Pardon wrote:
>>>> (snip)
>>>>> Well no matter what explanation you give to it, and I understand how it
>>>>> works,
>>>>
>>>> I'm not sure of this.

>
>>AP> Should I care about that?

>
> Yes, because as long as you don't understand it, you are in for unpleasant
> surprises.


Well if someone explains what is wrong about my understanding, I
certainly care about that (although I confess to sometimes being
impatient) but someone just stating he is not sure I understand?

>>>> It's not about "finding a name/identifier", it's about the difference
>>>> between (re)binding a name and mutating an object.

>
>>AP> The two don't contradict each other. Python has chosen that it won't
>>AP> rebind variables that are out of the local scope. So if the lefthand
>>AP> side of an assignment is a simple name it will only search in the
>>AP> local scope for that name. But if the lefthand side is more complicated
>>AP> if will also search the outerscopes for the name.

>
> No. It will always use the same search order.


So if I understand you correctly in code like:

c.d = a
b = a

All three names are searched for in all scopes between the local en global
one. That is what I understand with your statement that [python] always
uses the same search order.

My impression was that python will search for c and a in the total current
namespace but will not for b.

> But a variable that is bound
> inside the function (with an asignment) and is not declared global, is in
> the local namespace.


Aren't we now talking about implementation details? Sure the compilor
can set things up so that local names are bound to the local scope and
so the same code can be used. But it seems somewhere was made the
decision that b was in the local scope without looking for that b in
the scopes higher up.

Let me explain a bit more. Suppose I'm writing a python interpreter
in python. One implemantation detail is that I have a list of active
scopes which are directories which map names to objects. At the
start of a new function the scope list is adapted and all local
variables are inserted to the new activated scope and mapped to
some "Illegal Value" object. Now I also have a SearchName function
that will start at the begin of a scope list and return the
first scope in which that name exists. The [0] element is the
local scope. Now we come to the line "b = a"

This could be then executed internally as follows:

LeftScope = SearchName("b", ScopeList)
RightScope = SearchName("a", ScopeList)
LeftScope["b"] = RightScope["a"]

But I don't have to do it this way. I already know in which scope
"b" is, the local one, which has index 0. So I could just as well
have that line exucuted as follows:

LeftScope = ScopeList[0]
RightScope = SearchName("a", ScopeList)
LeftScope["b"] = RightScope["a"]

As far as I understand both "implementations" would make for
a correct execution of the line "b = a" and because of the
second possibility, b is IMO not conceptually searched for in
the same way as a is searched for, although one could organise
things that the same code is used for both.

Of course it is possible I completely misunderstood how python
is supposed to work and the above is nonesense in which case
I would appreciate it if you correct me.

>>AP> Python could have chosen an approach with a "nested" keyword, to allow
>>AP> rebinding a name in an intermediate scope. It is not that big a deal
>>AP> that it hasn't, but I keep finding the result strange and somewhat
>>AP> counterintuitive.

>
> Maybe it would have been nice if variables could have been declared as
> nested, but I think it shows that nested variables have to be used with
> care, similar to globals. Especially not allowing rebinding in intermediate
> scopes is a sound principle (`Nested variables considered harmful').
> If you need to modify the objects which are bound to names in intermediate
> scopes, use methods and give these objects as parameters.


But shouldn't we just do programming in general with care? And if
Nested variables are harmfull, what is then the big difference
between rebinding them and mutating them that we should forbid
the first and allow the second?

I understand that python evolved and that this sometimes results
in things that in hindsight could have been done better. But
I sometimes have the impression that the defenders try to defend
those results as a design decision. With your remark above I have
to wonder if someone really thought this through at design time
and came to the conclusion that nested variables are harmfull
and thus may not be rebound but not that harmfull so mutation
is allowed and if so how he came to that conclusion.


>>AP> Let me explain why:

>
>>AP> Suppose someone is rather new of python and writes the following
>>AP> code, manipulating vectors:

>
>>AP> A = 10 * [0]
>>AP> def f(B):
>>AP> ...
>>AP> for i in xrange(10):
>>AP> A[i] += B[i]
>>AP> ...

>
>>AP> Then he hears about the vector and matrix modules that are around.
>>AP> So he rewrites his code naively as follows:

>
>>AP> A = NullVector(10):
>>AP> def f(B):
>>AP> ...
>>AP> A += B
>>AP> ...

>
>>AP> And it won't work. IMO the principle of least surprise here would
>>AP> be that it should work.

>
> Well, A = f(B) introduces a local variable A. Therefore also A = A+B.
> And therefore also A += B.
>
> The only way to have no surprises at all would be if local variables would
> have to be declared explicitly, like 'local A'. But that would break
> existing code. Or maybe the composite assignment operators should have been
> exempted. Too late now!


Let me make one thing clear. I'm not trying to get the python people to
change anything. Most I hope for is that they would think about this
behaviour for python 3000.

> You have the same problem with:
>
> A = [10]
> def inner():
> A.append(2)
>
> works but
>
> A = [10]
> def inner():
> A += [2]
>
> doesn't. The nasty thing in this example is that although A += [2] looks
> like a rebinding syntactically, semantically there is no rebinding done.
>
> I think the cleaner solution is (use parameters and don't rebind):
> def inner(A):
> A.append(2)
>


For what it is worth. My proposal would be to introduce a rebinding
operator, ( just for the sake of this exchange written as := ).

A line like "b := a", wouldn't make b a local variable but would
search for the name b in all active scopes and then rebind b
there. In terms of my hypothetical interpreter something like
the above.

LeftScope = SearchName("b", ScopeList)
RightScope = SearchName("a", ScopeList)
LeftScope["b"] = RightScope["a"]

With the understanding that b wouldn't be inserted in the local
scope unless there was an assignment to be somewhere else in
the function.

The augmented assignments could then be redefined in terms of the
rebinding operator instead of the assignment.

I'm not sure this proposal would eliminate all surprises but as
far as I can see it wouldn't break existing code. But I don't think
this proposal would have a serious chance.

In any case thanks for your contribution.

--
Antoon Pardon
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Re: [Python-Dev] The baby and the bathwater (Re: Scoping,augmented assignment, 'fast locals' - conclusion) Josiah Carlson Python 4 06-19-2006 01:34 PM
Augmented assignment Suresh Jeevanandam Python 8 02-21-2006 07:20 PM
User-defined augmented assignment Pierre Barbier de Reuille Python 4 10-01-2005 11:58 PM
Augmented Assignment question Doug Tolton Python 6 07-19-2003 12:52 AM
RE: Augmented Assignment question Delaney, Timothy C (Timothy) Python 0 07-17-2003 12:25 AM



Advertisments