Python闭包函数失去对外部变量的访问

16

我刚学Python的装饰器,感觉很酷,但很快我发现我的修改后的代码出现了奇怪的问题。

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

我调试它时,在打印param1时抛出异常。

UnboundLocalError: local variable 'param1' referenced before assignment
如果我删除这一行 param1 = 'new',并且没有对外部作用域变量进行任何修改操作(链接到新对象),那么这个程序可能会正常工作。
这是否意味着我只复制了外部作用域变量的一个副本,然后进行了修改?
感谢 Delnan,这是闭包的关键。 可能的答案在这里: Python中闭包与语言X闭包相比有哪些限制? 类似的代码如下:
def e(a):
    def f():
        print a
        a = '1'
    f()
e('2')

而且这似乎是以前让人烦恼的全局变量:

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

通过添加global符号可以解决这个问题。但是对于闭包,没有这样的符号。感谢unutbu,Python 3 给了我们nonlocal

我知道从上面所述直接访问外部变量是只读的。但是在使用preceded(先前)读取变量(print var)时也受到影响,这种情况有些令人不舒服。


这与装饰器完全无关。 - user395760
是的,这种情况发生在闭包中。 像这样: def e(a): def f(): print a a = '1' f() e('123') - V.E.O
为什么你需要给同一个名称赋值?为什么不使用 param3 = new ... fn(param3) 呢? - detly
嗨detly,这只是一个演示,用于打印和分配一个参数。如果此变量来自任何外部作用域,则问题仍然存在。 - V.E.O
3个回答

22
当Python解析一个函数时,如果它发现一个变量在赋值语句的左侧使用,例如:
param1 = 'new'

它假定所有这样的变量都是函数局部的。 因此,当您在这个赋值语句之前加上

print param1

由于 Python 在执行 print 语句时没有该本地变量的值,因此会出现错误。


在 Python3 中,您可以通过声明 param1 为非局部变量来解决此问题:

def with_wrapper(param1):
    def dummy_wrapper(fn):
        nonlocal param1
        print param1
        param1 = 'new'
        fn(param1)
    return dummy_wrapper
在Python2中,你必须采用一种技巧,例如传递一个列表(或其他可变对象)内的param1:
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

1
print 语句放在赋值语句之后? - kindall
谢谢您的解释。但我不知道为什么param1 = 'new'会重新分配外部参数,而不是创建一个本地变量。 - V.E.O
3
也许你和我作为人类更喜欢Python先检查外部作用域中是否有“param1”变量,如果存在则使用它,仅当不存在时才假定它是本地变量。但这会迫使Python等到运行时才决定函数的本地变量,这会显著减慢Python的性能。每次调用函数时,本地变量都必须重新确定。因此,Python在解析代码块时即执行def块时,使用赋值来识别本地变量,而不是在调用函数时进行判断。 - unutbu
@unutbu 谢谢,现在我认为这种优化方法是可接受的。 - V.E.O
@unutbu 不需要在运行时动态确定,Python和JavaScript都有普通变量的词法作用域(Python中的self或JavaScript中的this是完全不同的东西),这只是一个历史性的设计决策,解释在https://www.python.org/dev/peps/pep-3104。 - Aprillion

0

在函数中,您将param1赋值给一个局部变量。然而,在打印它的时候,它还没有被赋值,因此会出现错误。Python不会回退到外部作用域查找变量。


“print param1” 是一个例子,在实际代码中,当“param1 = xx”存在时,任何对“param1”的引用都是被禁止的。 - V.E.O

0

方便处理临时情况或探索性代码(但不是良好的实践)

如果你想在Python 2.x中从外部作用域中捕获一个变量,使用global也是一个选项(具有通常的限制条件)。

以下代码将抛出异常(在if条件中,内部对outer1的赋值使其成为局部变量,因此未定义):

def outer():
    outer1 = 1
    def inner():
        if outer1 == 1:
            outer1 = 2
            print('attempted to accessed outer %d' % outer1)

这不会:

def outer():
    global outer1
    outer1 = 1
    def inner():
        global outer1
        if outer1 == 1:
            outer1 = 2
            print('accessed outer %d' % outer1)

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