pythonpython-2.7scopepython-nonlocal

How to change outer function variabe?


In "Learning Python" by Mark Lutz I read: "Functions can freely use names assigned in syntactically enclosing functions and the global scope, but they must declare such nonlocals and globals in order to change them" I failed to test them in Python 2.7

def f1():
    f1_a = 'f1_a'
    def f2():
 #       global f1_a
 #       nonlocal f1_a
        f2_a = 'f2_a'
        print 'f2_a={:s}'.format(f2_a)
        print 'f1_a={:s}'.format(f1_a)
        f1_a = 'f1f2_a'

    f2()
    print 'f1_a={:s}'.format(f1_a)

>>> f1()

gives the error:

UnboundLocalError: local variable 'f1_a' referenced before assignment

'global' (NameError: global name 'f1_a' is not defined) and 'nonlocal' (nonlocal f1_a , SyntaxError: invalid syntax) doesn't work. Does this mean that there is no way to change a variable introduces in an outer function from the inner (immediately enclosed) one?


Solution

  • For your code to work, you'd want the nonlocal keyword, but that only exists in Python 3. If you're using Python 2, you'll need to use some kind of workaround instead.

    One option is to put the value in the outer function inside a mutable container, such as a list. The inner function can mutate the container in place, even if it can't rebind the variable:

    def f1():
        f1_a = ['f1_a']     # add a list around the value
        def f2():
            f2_a = 'f2_a'
            print 'f2_a={:s}'.format(f2_a)
            print 'f1_a={:s}'.format(f1_a[0])   # read the value from inside the list!
            f1_a[0] = 'f1f2_a'       # mutate the list in place
    
        f2()
        print 'f1_a={:s}'.format(f1_a[0])   # read from the list here too
    

    While the code above does work, I'd strongly recommend that you upgrade to Python 3, unless you absolutely need to remain with Python 2 for backwards compatibility, or because a dependency is not yet ported to Python 3. The dependency situation is much better these days as almost every project that is actually being maintained has been ported to Python 3, so if you're using something that has not been ported, it's probably not getting any bug fixes either. Almost all new code should target Python 3 exclusively.