Python nested functions variable scoping
--
Track title: CC I Beethoven Sonata No 31 in A Flat M
--
Chapters
00:00 Question
01:12 Accepted answer (Score 73)
02:07 Answer 2 (Score 356)
04:45 Answer 3 (Score 193)
05:47 Answer 4 (Score 34)
06:18 Thank you
--
Full question
https://stackoverflow.com/questions/5218...
Accepted answer links:
[The documentation about Scopes and Namespaces]: http://docs.python.org/tutorial/classes....
Answer 2 links:
[nonlocal]: http://docs.python.org/py3k/reference/si...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#python #variables #scope
#avk47
ANSWER 1
Score 400
In Python 3, you can use the nonlocal statement to access non-local, non-global scopes.
The nonlocal statement causes a variable definition to bind to a previously created variable in the nearest scope. Here are some examples to illustrate:
def sum_list_items(_list):
total = 0
def do_the_sum(_list):
for i in _list:
total += i
do_the_sum(_list)
return total
sum_list_items([1, 2, 3])
The above example will fail with the error: UnboundLocalError: local variable 'total' referenced before assignment
Using nonlocal we can get the code to work:
def sum_list_items(_list):
total = 0
def do_the_sum(_list):
# Define the total variable as non-local, causing it to bind
# to the nearest non-global variable also called total.
nonlocal total
for i in _list:
total += i
do_the_sum(_list)
return total
sum_list_items([1, 2, 3])
But what does "nearest" mean? Here is another example:
def sum_list_items(_list):
total = 0
def do_the_sum(_list):
# The nonlocal total binds to this variable.
total = 0
def do_core_computations(_list):
# Define the total variable as non-local, causing it to bind
# to the nearest non-global variable also called total.
nonlocal total
for i in _list:
total += i
do_core_computations(_list)
do_the_sum(_list)
return total
sum_list_items([1, 2, 3])
In the above example, total will bind to the variable defined inside the do_the_sum function, and not the outer variable defined in the sum_list_items function, so the code will return 0. Note that it is still possible to do double nesting such as this: if total is declared nonlocal in do_the_sum the above example would work as expected.
def sum_list_items(_list):
# The nonlocal total binds to this variable.
total = 0
def do_the_sum(_list):
def do_core_computations(_list):
# Define the total variable as non-local, causing it to bind
# to the nearest non-global variable also called total.
nonlocal total
for i in _list:
total += i
do_core_computations(_list)
do_the_sum(_list)
return total
sum_list_items([1, 2, 3])
In the above example the nonlocal assignment traverses up two levels before it locates the total variable that is local to sum_list_items.
ANSWER 2
Score 198
Here's an illustration that gets to the essence of David's answer.
def outer():
a = 0
b = 1
def inner():
print a
print b
#b = 4
inner()
outer()
With the statement b = 4 commented out, this code outputs 0 1, just what you'd expect.
But if you uncomment that line, on the line print b, you get the error
UnboundLocalError: local variable 'b' referenced before assignment
It seems mysterious that the presence of b = 4 might somehow make b disappear on the lines that precede it. But the text David quotes explains why: during static analysis, the interpreter determines that b is assigned to in inner, and that it is therefore a local variable of inner. The print line attempts to print the b in that inner scope before it has been assigned.
ACCEPTED ANSWER
Score 74
When I run your code I get this error:
UnboundLocalError: local variable '_total' referenced before assignment
This problem is caused by this line:
_total += PRICE_RANGES[key][0]
The documentation about Scopes and Namespaces says this:
A special quirk of Python is that – if no
globalstatement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects.
So since the line is effectively saying:
_total = _total + PRICE_RANGES[key][0]
it creates _total in the namespace of recurse(). Since _total is then new and unassigned you can't use it in the addition.
ANSWER 4
Score 17
This is a variation of redman's solution, but using a proper namespace instead of an array to encapsulate the variable:
def foo():
class local:
counter = 0
def bar():
print(local.counter)
local.counter += 1
bar()
bar()
bar()
foo()
foo()
I'm not sure if using a class object this way is considered an ugly hack or a proper coding technique in the python community, but it works fine in python 2.x and 3.x (tested with 2.7.3 and 3.2.3). I'm also unsure about the run-time efficiency of this solution.