6

Have a look at this code:

def closure():
    value = False

    def method_1():
        value = True

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

    method_1()
    method_2()

closure()

I would expect it to print 'Value is: True' but it doesn't. Why is this and what is the solution?

Lee Treveil
  • 6,655
  • 4
  • 30
  • 29

5 Answers5

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
Community
  • 1
  • 1
Cameron
  • 96,106
  • 25
  • 196
  • 225
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
senderle
  • 145,869
  • 36
  • 209
  • 233
  • Note that with `global` it's not part of the closure any more and it can be messed up elsewhere in the program. – agf Jul 26 '11 at 15:00
  • @agf -- quite so; I'm just trying to explain the scoping rules in as general a way as possible. – senderle Jul 26 '11 at 15:05
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.

agf
  • 171,228
  • 44
  • 289
  • 238
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.

Community
  • 1
  • 1
Nate
  • 12,499
  • 5
  • 45
  • 60
1

To avoid this, you can use a list.

value = [False]

def method_1():
    value[0] = True

What Python does now is searching in higher levels of the scope as value is not available in the local variables. As value is a list and Python refernces it as a global variable relative to *method_1*, you can treat value as it is, a list.

Niklas R
  • 16,299
  • 28
  • 108
  • 203