Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nested function change variable in an outside function not working

def some_func(a): 
    def access_a():
        print(a)
    access_a()

outputs the value of a. However, if I want to change a in the nested function like this:

def some_func(a): 
    def change_a():
        a += 1
        print(a)
    change_a()

it raises UnboundLocalError exception.

I know a is a nonlocal variable, but why can I access it without declaring nonlocal a?

like image 797
herbert Avatar asked Dec 22 '16 08:12

herbert


2 Answers

Python scoping rules 101:

  1. a name bound in a function body is considered local unless explicitely declared global (Python 2.x and 3.x) or nonlocal (Python 3.x only). This holds true whereever the assignment happens in the function's body. Trying to read a local variable before it's bound is of course an error.

  2. if a name is read but not bound in a function's body, it will be looked up in enclosing scopes (outer function(s) if any then global scope). NB: functions arguments are de facto local names so they will never be looked up in enclosing scopes.

Note that a += 1 is mainly a shortcut for a = a + 1, so in your example a is local (bound in the function's body and not explicitely declared global or nonlocal), but you try to read it (the rhs of a = a+1) before it's bound.

In Python 3 you can solve this with a nonlocal statement:

>>> def outer(a):
...    def change():
...       nonlocal a
...       a += 1
...    print("before : {}".format(a))
...    change()
...    print ("after : {}".format(a))
... 
>>> outer(42)
before : 42
after : 43

Python 2 doesn't have nonlocal so the canonical hack is to wrap the variable in a mutable container (typically a list but any mutable object would do):

>>> def outer(a):
...     _a = [a]
...     def change():
...         _a[0] += 1
...     print("before : {}".format(_a[0]))
...     change()
...     print ("after : {}".format(_a[0]))
... 
>>> outer(42)
before : 42
after : 43

which is quite ugly to say the least.

Now while closures are quite handy, they are mostly the functional counterpart of objects : a way to share state between a set of functions while preserving encapsulation of this state, so if you find you have a need for a nonlocal variable perhaps a proper class might be a cleaner solution (though possibly not for your example that doesn't return the inner function but only uses it internally).

like image 132
bruno desthuilliers Avatar answered Nov 05 '22 03:11

bruno desthuilliers


i have two solutions for you:

#first one:
# try with list, compound data types dict/list
def some_func(a): 
    def change_a():
        a[0] += 1
        print(a[0])
    change_a()
some_func([1])
>>> 2


#second one
#reference pointer 
from ctypes import *
def some_func_ctypes(a):
    def change_a():
      a[0] += 1
      print a.contents, a[0]
    change_a()

i = c_int(1)
pi = pointer(i)
some_func_ctypes(pi)

>>> c_int(2) 2
like image 42
Ari Gold Avatar answered Nov 05 '22 04:11

Ari Gold