在Python闭包中出现了UnboundLocalError:引用之前赋值的本地变量。

8
我将在Python中实现两个简单的闭包。对我来说,它们看起来是一样的,但一个可以运行,而另一个则不能。
有效的代码如下:
def makeInc(x, y):
    def inc():
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)

inc5 () # returns 15
inc10() # returns 15

但是第二个不起作用:
import os
def linker(dest, filename):
    print filename
    def link(): 
        if os.path.isfile(filename): # line 17
            filename = os.path.join(os.getcwd(), filename)
            dest = os.path.join(dest, filename)
            y = rawinput('[y]/n: ln -sf %s %s' % (dest, filename))
            if y == 'n':
                return 1
            else:
                return os.system('ln -sf %s %s' %(dest, filename))
        else:
            return -1
    return link

l = linker('~', '.vimrc')
l()  # line 30

当执行l()时,在link()的第一行出现故障:

Traceback (most recent call last):
  File "test.py", line 30, in <module>
    l()
  File "test.py", line 17, in link
    if os.path.isfile(filename):
UnboundLocalError: local variable 'filename' referenced before assignment

他们在我看来是完全相同的,所以我不明白为什么第二个不能运行。有任何想法吗?

值得一读:https://dev59.com/J3VC5IYBdhLWcg3wcgud#292502 赋值隐式地为整个函数的作用域创建了一个本地变量。这就像在函数的第一行有一个隐藏的 local filename(注意:local 不是 Python 的实际关键字,但我只是将其与 global 进行比较)。由于 Python 使用 LEGB,它会首先找到本地变量,即使在分配之前,而不是查找封闭作用域,它会给出一个错误。 - Shashank
1个回答

7
您已经用 filename = os.path.join(os.getcwd(), filename) 覆盖了变量,如果您将 filename = 更改为除 filename 之外的其他内容,则不会出现 local variable 'filename' referenced before assignment 错误。
一旦设置了 filename =,您就不再引用传递的参数 filename,而是引用内部函数作用域中的本地 filename。在 if 语句之前使用它定义之前,您会遇到相同的问题。
如果您将两行代码和其他变量更改为以下内容,则会遇到相同的问题:
filename_ = os.path.join(os.getcwd(), filename)
dest_ = os.path.join(dest, filename)

你会发现,现在filename指的是参数而不是在内部函数中定义的局部变量,代码运行良好。
如果你尝试在第一个函数中重新分配x,并在定义之前访问x,你会看到完全相同的行为:
def makeInc(x, y):
    def inc():
        print  y + x # will cause referenced before assignment error
        x = 5 # now x is local to the inner func, the x from the outer function is overridden
        return y + x
    return inc

如果您打印__closure__属性,您将看到发生了什么:
def makeInc(x, y):
    def inc():
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)
print(inc5.__closure__)
(<cell at 0x7f180df67e50: int object at 0xef00f8>, <cell at 0x7f180df67fa0: int object at 0xef0080>)

现在正在重新分配x:

def makeInc(x, y):
    def inc():
        print  y + x
        x= 5
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)
print(inc5.__closure__)
(<cell at 0x7fea11889fd8: int object at 0x291e080>,)

在内部函数重新分配后,就不再有对 x 的引用。
因此,你最初的两个函数之间的根本区别在于一个在本地作用域中重新分配变量,而另一个则没有。如上面的代码所示,如果在第一个函数中执行类似的操作,则结果完全相同。
这里有一个关于作用域LEGB等的不错教程:链接

非常有帮助和深刻的见解。谢谢! - qweruiop
3
如果您不想定义一个新变量,解决方案是在 link 函数顶部使用 nonlocal filename - solarc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接