10

在 python 中,我可以写:

def func():
    x = 1
    print x
    x+=1

    def _func():
        print x
    return _func

test = func()
test()

当我运行它时,输出是:

1

2

因为 _func 可以访问 func 中定义的“x”变量。正确的...

但如果我这样做:

def func():
    x = 1
    print x

    def _func():
        x+=1
        print x
    return _func

test = func()
test()

然后我收到一条错误消息:UnboundLocalError: local variable 'x' referenced before assignment

在这种情况下,似乎 _func 无法“看到”“x”变量

问题是:为什么在第一个示例中打印 x 会“看到”“x”变量,而数学运算符 x+=1 会抛出异常?

我不明白为什么...

4

3 回答 3

8

检查这个答案:https ://stackoverflow.com/a/293097/1741450

可以访问除局部函数变量之外的范围内的变量,但如果没有进一步的语法,则不能将其重新绑定到新参数。相反,赋值将创建一个新的局部变量,而不是影响父作用域中的变量。例如:

于 2013-09-04T14:11:50.217 回答
3

Considering that Python is an "interpreted" language, one naturally assumes that if a variable is already defined in an outer scope that same variable should still be accessible in the inner scope. And indeed in your first example when the inner _func merely prints x it works.

The thing that's non-obvious about Python though is that this is not exactly how the scope of a variable is determined. Python analyzes at compile time which variables should be considered "local" to a scope based on whether they're assigned to within that scope. In this case assignment includes augmented assignment operators. So when Python compiles your inner _func in the second example to bytecode it sees the x += 1 and determines that x must be a local variable to _func. Except of course since its first assignment is an augmented assignment there is no x variable locally to augment and you get the UnboundLocalError.

A different way to see this might be to write something like:

def _func():
    print x
    x = 2

Here again because _func contains the line x = 2 it will treat x as a local variable in the scope of that function and not as the x defined in the outer function. So the print x should also result in an UnboundLocalError.

You can examine this in deeper detail by using the dis module to display the bytecode generated for the function:

>>> dis.dis(_func)
  2           0 LOAD_FAST                0 (x)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  3           5 LOAD_CONST               1 (2)
              8 STORE_FAST               0 (x)
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE

The LOAD_FAST opcode is for intended for "fast" lookups of local variables that bypasses slower, more general name lookups. It uses an array of pointers where each local variable (in the current stack frame) is associated with an index in that array, rather than going through dictionary lookups and such. In the above example the sole argument to the LOAD_FAST opcode is 0--in this case the first (and only) local.

You can check on the function itself (specifically its underlying code object) that there is one local variable used in that code, and that the variable name associated with it is 'x':

>>> _func.__code__.co_nlocals
1
>>> _func.__code__.co_varnames
('x',)

That's how dis.dis is able to report 0 LOAD_FAST 0 (x). Likewise for the STORE_FAST opcode later on.

None of this is necessary to know in order to understand variable scopes in Python, but it can be helpful nonetheless to know what's going on under the hood.

As already mentioned in some other answers, Python 3 introduced the nonlocal keyword which prevents this compile-time binding of names to local variables based on assignment to that variable in the local scope.

于 2013-09-04T14:22:34.010 回答
0

您不能重新绑定在 Python 2 中关闭的变量(您可以nonlocal在 Python 3 中使用)。如果我必须做你在 Python 2 中所做的事情,那么我会执行以下解决方法:

class Pointer(object):
    def __init__(self, val):
        self.val = val

def func():
    px = Pointer(1)
    print px.val
    px.val += 1

    def _func():
        print px.val
    return _func

test = func()
test()

基本上,我将需要变异的任何值放到一个对象上——有时我使用一个包含一个元素的列表——然后重新编写代码,这样发生的所有事情就是方法调用。上面实际上等价于:

class Pointer(object):
    def __init__(self, val):
        self.val = val

def func():
    px = Pointer(1)
    print px.val
    px.__setattr__('val', px.val + 1)

    def _func():
        print px.val
    return _func

test = func()
test()
于 2013-09-04T14:17:31.187 回答