分配给外部数组时,python闭包/嵌套函数失败

python closure/nested function fail when with assignment to outer array

本问题已经有最佳答案,请猛点这里访问。

如果符号引用分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();

结果是:

1
2
3
4
5
6
7
8
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

如果我只是为了测试而更换:

1
2
 -       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]) ]

错误是不可模仿的,但是代码不是我想要的。上面的行为是用python闭包/嵌套函数实现的吗?是否需要包装数组以在对象内进行修改并只调用函数?

另一方面,这项工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();


因为您在touch中分配了p,所以它在touch中成为局部变量,并有效地"隐藏"了封闭范围中的所有其他名称p。为了告诉python您实际上想要引用outer中的p,您应该使用nonlocal p,例如:

1
2
3
4
5
6
7
8
9
10
11
12
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()

您的第二个示例之所以有效,是因为您在两种情况下都引用touch的属性,而不是进行赋值(p = ...)。

参见python参考文档中的nonlocal、作用域的参考文档以及提出nonlocal语法的PEP 3104。nonlocal只存在于python 3中,但如果需要使用python 2,则有一个解决方法。