The Python Oracle

Python and closed variables

Become part of the top 3% of the developers by applying to Toptal https://topt.al/25cXVn

--

Music by Eric Matyas
https://www.soundimage.org
Track title: Book End

--

Chapters
00:00 Question
00:30 Accepted answer (Score 17)
01:39 Answer 2 (Score 2)
02:12 Answer 3 (Score 2)
02:50 Answer 4 (Score 2)
03:42 Thank you

--

Full question
https://stackoverflow.com/questions/6831...

Accepted answer links:
[nonlocal]: https://stackoverflow.com/questions/1261...
[docs]: http://docs.python.org/release/3.0.1/ref...

Answer 2 links:
[good example in this answer]: https://stackoverflow.com/questions/3190...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #closures #python2x

#avk47



ACCEPTED ANSWER

Score 17


This happens because method_1 gets its own local scope where it can declare variables. Python sees value = True and thinks you're creating a new variable named value, local to method_1.

The reason Python does this is to avoid polluting the outer scope's locals with variables from an inner function. (You wouldn't want assignments in regular, module-level functions to result in global variables being created!)

If you don't assign to value, then Python searches the outer scopes looking for the variable (so reading the variable works as expected, as demonstrated by your method_2).

One way to get around this is by using a mutable object instead of assigment:

result = { 'value': False }

def method_1():
    result['value'] = True

In Python 3, the nonlocal statement (see also docs) was added for exactly this scenario:

def method_1():
    nonlocal value
    value = True    # Works as expected -- assigns to `value` from outer scope



ANSWER 2

Score 2


In method_1, Python assumes (quite sensibly!) that value is a local variable. Whenever you assign to a variable name inside a function, it is assumed that that variable name is a new local variable. If you want it to be global, then you have to declare it as global, and if you want it to be "nonlocal", in Python 3, you can declare it nonlocal, but in Python 2, you have to do something uglier: store the value in a container. That avoids having to reassign the variable name, and so avoids the scoping ambiguity.

def method_1_global():
    global value
    value = True

def method_1_nonlocal_P3():
    nonlocal value
    value = True

value = [False]
def method_1_nonlocal_P2():
    value[0] = True



ANSWER 3

Score 2


When you assign to a variable, it assumes the variable is of local scope. So the value in method_1 is not the value in closure.

If you want this to work on Python 3, add a line to method_1: nonlocal value.

On Python 2,

def closure():
    value = [False]

    def method_1():
        value[0] = True

    def method_2():
        print 'value is:', value

    method_1()
    method_2()

closure()

is one possible work-around.




ANSWER 4

Score 2


This is because you are not actually modified the closed-over variable - you are masking it with a new one that has the same name. In python 2.x there is no easy way around this, which is why the nonlocal keyword was added in python 3.

This can be worked around, however, using mutable types like list and dictionary. There is a good example in this answer.