Python内部函数

Python inner functions

在python中,我可以写:

1
2
3
4
5
6
7
8
9
10
11
def func():
    x = 1
    print x
    x+=1

    def _func():
        print x
    return _func

test = func()
test()

当我运行它时,输出是:

因为func可以访问func中定义的"x"变量。正确的。。。

但如果我这样做:

1
2
3
4
5
6
7
8
9
10
11
def func():
    x = 1
    print x

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

test = func()
test()

然后我收到一条错误消息:unboundlocalerror:在赋值之前引用了局部变量"x"

在这种情况下,似乎func看不到"x"变量

问题是:为什么第一个示例中的print x"看到"了"x"变量,而数学运算符x+=1抛出了一个异常?

我不明白为什么…


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

Variables in scopes other than the local function's variables can be accessed, but can't be rebound to new parameters without further syntax. Instead, assignment will create a new local variable instead of affecting the variable in the parent scope. For example:


考虑到python是一种"解释"语言,我们自然会假设,如果一个变量已经在外部范围中定义,那么在内部范围中仍然可以访问该变量。实际上,在第一个例子中,当内部的_func仅仅打印x时,它就工作了。

但是,关于python不明显的一点是,这并不是确定变量范围的确切方法。python在编译时根据变量是否在某个范围内被分配,分析哪些变量应该被认为是"局部"的。在这种情况下,赋值包括增广的赋值运算符。因此,在第二个示例中,当Python将内部的_func编译为字节码时,它会看到x += 1,并确定x必须是_func的局部变量。当然,除了它的第一个赋值是一个增广赋值之外,在本地没有用于增广的x变量,您就得到了UnboundLocalError

另一种看问题的方法可能是写如下内容:

1
2
3
def _func():
    print x
    x = 2

这里再次说明,由于_func包含行x = 2,它将把x视为该函数范围内的局部变量,而不是外部函数中定义的x。因此,print x也应该产生UnboundLocalError

您可以使用dis模块来显示为函数生成的字节码,从而更详细地检查这一点:

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

LOAD_FAST操作码用于对本地变量进行"快速"查找,从而绕过更慢、更一般的名称查找。它使用一个指针数组,其中每个局部变量(在当前堆栈帧中)都与该数组中的索引相关联,而不是进行字典查找等。在上面的例子中,LOAD_FAST操作码的唯一参数是0--在这种情况下,第一个(也是唯一的)本地操作码。

您可以检查函数本身(特别是它的基础代码对象),该代码中使用了一个局部变量,并且与之关联的变量名是'x'

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

这就是dis.dis报告0 LOAD_FAST 0 (x)的方法。同样适用于稍后的STORE_FAST操作码。

为了理解Python中的变量范围,这些都不是必需的,但是了解引擎盖下发生了什么仍然是有帮助的。

正如在其他一些答案中已经提到的,python 3引入了nonlocal关键字,它可以防止在编译时根据对本地范围内变量的赋值将名称绑定到本地变量。


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

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

基本上,我把需要改变的任何值放到一个对象上——有时我使用一个元素列表——然后重新编写代码,这样所发生的一切就是方法调用。以上实际上相当于:

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