1

It seems that the closure of a python function has problems if the symbol referenced is assigned:

def outer():
    p = []
    def gen():
        def touch(e):
            if e[0] == 'add':
                p.append(e);
            elif e[0] == 'rem':
                p = [ x for x in p if not (x[1] == e[1]) ]
        return touch
    f = gen()
    for i in [["add","test1"],["add","test2"],["rem","test2"],["rem","test1"]]:
        f(i)      

outer();

and the result is:

Traceback (most recent call last):
  File "b.py", line 22, in <module>
    outer();
  File "b.py", line 20, in outer
    f(i)      
  File "b.py", line 14, in touch
    p.append(e);
UnboundLocalError: local variable 'p' referenced before assignment

If I just for test replace:

 -       p = [ x for x in p if not (x[1] == e[1]logig is) ]                                                                                                                                
 +       a = [ x for x in p if not (x[1] == e[1]) ]                                                                                                                                

the error dissapears, however the code is not what I want. Is the above behaviour expected with python closures/nested functions? Do I need to wrap the array to modify inside a object and just call functions?

On the other hand this one works:

class o():
    def __init__(self):
        self.p = []
    def add(self,e):
        self.p.append(e);
    def rem(self,e):
        self.p = [ x for x in self.p if not (x[1] == e[1]) ]

def outer():
    p = o()
    def gen():
        def touch(e):
            if e[0] == 'add':
                p.add(e);
            elif e[0] == 'rem':
                p.rem(e)
        return touch
    f = gen()
    for i in [["add","test1"],["add","test2"],["rem","test2"],["rem","test1"]]:
        f(i)      

outer();
Konrad Eisele
  • 3,088
  • 20
  • 35

1 Answers1

2

Because you are assigning p inside touch, it becomes a local variable in touch and effectively "hides" all other names p in enclosing scopes. In order to tell Python that you actually want to refer to the p within outer, you should use nonlocal p, for example:

def outer():
    p = []
    def touch(e):
        # The following line is required to refer to outer's p
        nonlocal p
        if e[0] == 'add':
            p.append(e)
        elif e[0] == 'rem':
            p = [ x for x in p if not (x[1] == e[1]) ]
    for i in [["add","test1"],["add","test2"],["rem","test2"],["rem","test1"]]:
        touch(i)
outer()

Your second example works because you are referring to an attribute of p in both cases of touch, rather than making an assignment (p = ...).

See nonlocal in the Python reference documentation, the reference documentation for scopes, and PEP 3104 in which the nonlocal syntax was proposed. nonlocal only exists in Python 3, but there is a workaround if it is necessary to use Python 2.

EE_
  • 126
  • 5