0

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?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153

3 Answers3

2

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.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • So far I haven't managed to summon enough courage to migrate to Python 3 . Missed 'nonlocal' was the last straw . – Vladimir Zolotykh Apr 11 '18 at 08:41
  • I failed to migrate to Python 3 and returned back to 2.7 . I grew accustomed to using Elpy and the task of switching from Python 2.7 to Python 3 in the Elpy proved to hard for me to solve. – Vladimir Zolotykh Apr 11 '18 at 11:16
1
def f1():
    f1_a = 'f1_a'
    def f2():

        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()

this will work on python 3 or later because nonlocal will work only python 3 or latter

python 2 you can archive this by this way

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

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

f1()

both will out put

f2_a=f2_a
f1_a=f1_a
f1_a=f1f2_a

enter image description here

using empty class

class emptyClass: pass
def f1():
    emp = emptyClass()
    emptyClass.f1_a = 'f1_a'
    def f2():
        f2_a = 'f2_a'
        print('f2_a={:s}'.format(f2_a))
        print('f1_a={:s}'.format(emptyClass.f1_a))
        emptyClass.f1_a = 'f1f2_a'

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

f1()
  • Sorry, f1.f1_a doesn't work outside f2 . I still get 'f1_a=f1_a' in f1 after calling f2() – Vladimir Zolotykh Apr 11 '18 at 08:37
  • Above should work or else you can create a empty class and use that but above way is clean(all these are for python 2.x for python 3 you can use `nonlocal`) . I updated the answer. after call f2() inside f1() . `f1.f1_a` becomes the value assigned inside the f2() function – Sunimal S.K Malkakulage Apr 11 '18 at 09:08
1

Blcknght answer gives a way to achieve what you want, but I want to emphasize that your exception happens because you rebinding f1_a variable (which defined in f1) inside f2 function.

You do have access to outer function variables from the enclosed one.

If you remove your new binding of f1_a in the inner function, your code works in python 2.7:

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

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

>>> f1()
f1_a=f1_a
f2_a=f2_a
f1_a=f1_a

This snippet shows that

Your code breaks once you rebinding f1_a to another object

So, regarding your original question - you can access outer function attributes from the inner function. You can also mutate the values inside a container. But rebinding an immutable variable is causing your issue.

Chen A.
  • 10,140
  • 3
  • 42
  • 61
  • I was interested whether it is possible to change the outer variable in Python 2.7 . Since I was told it is not possible I have to consider migrating into Python 3 . – Vladimir Zolotykh Apr 11 '18 at 08:47
  • You can if the outer function value is mutuable (list, stacks, etc) – Chen A. Apr 11 '18 at 09:29