Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python closure function losing outer variable access

I just learned python @ decorator, it's cool, but soon I found my modified code coming out weird problems.

def with_wrapper(param1):
    def dummy_wrapper(fn):
        print param1
        param1 = 'new'
        fn(param1)
    return dummy_wrapper

def dummy():
    @with_wrapper('param1')
    def implementation(param2):
        print param2

dummy()

I debug it, it throws out exception at print param1

UnboundLocalError: local variable 'param1' referenced before assignment

If I remove param1 = 'new' this line, without any modify operation (link to new object) on variables from outer scope, this routine might working.

Is it meaning I only have made one copy of outer scope variables, then make modification?


Thanks Delnan, it's essential to closure. Likely answer from here: What limitations have closures in Python compared to language X closures?

Similar code as:

def e(a):
    def f():
        print a
        a = '1'
    f()
e('2')

And also this seems previous annoying global variable:

a = '1'
def b():
    #global a
    print a
    a = '2'
b()

This is fixed by add global symbol. But for closure, no such symbol found. Thanks unutbu, Python 3 gave us nonlocal.

I know from above directly accessing to outer variable is read-only. but it's kind of uncomfortable to see preceded reading variable(print var) is also affected.

like image 345
V.E.O Avatar asked Aug 29 '12 16:08

V.E.O


1 Answers

When Python parses a function, it notes whenever it finds a variable used on the left-hand side of an assignment, such as

param1 = 'new'

It assumes that all such variables are local to the function. So when you precede this assignment with

print param1

an error occurs because Python does not have a value for this local variable at the time the print statement is executed.


In Python3 you can fix this by declaring that param1 is nonlocal:

def with_wrapper(param1):
    def dummy_wrapper(fn):
        nonlocal param1
        print param1
        param1 = 'new'
        fn(param1)
    return dummy_wrapper

In Python2 you have to resort to a trick, such as passing param1 inside a list (or some other mutable object):

def with_wrapper(param1_list):
    def dummy_wrapper(fn):
        print param1_list[0]
        param1_list[0] = 'new'   # mutate the value inside the list
        fn(param1_list[0])
    return dummy_wrapper

def dummy():
    @with_wrapper(['param1'])   # <--- Note we pass a list here
    def implementation(param2):
        print param2
like image 73
unutbu Avatar answered Sep 24 '22 17:09

unutbu