【问题标题】:Python inner functionsPython内部函数
【发布时间】:2013-09-08 02:13:56
【问题描述】:

在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”变量

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

我不明白为什么......

【问题讨论】:

  • 这就是为什么 python 3 添加了nonlocal
  • 我通常使用对象来存储上下文,因为您可以在闭包内修改对象的属性。例如:x = {'val': 0} 然后 x['val'] += 1_func 内。
  • 还有一个有趣的question
  • 这是许多其他问题的重复,例如this onethis one。在此站点上搜索 UnboundLocalError 会为您指出答案。 “python UnboundLocalError”的first google result 也详细解释了发生的情况。请在下次提问之前做一些研究......

标签: python


【解决方案1】:

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

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

【讨论】:

  • 是的:仅打印访问 x(它可以),而在第二个示例中,该函数试图修改 x(它不能根据我的报价)。
【解决方案2】:

考虑到 Python 是一种“解释型”语言,人们自然会假设,如果一个变量已经在外部作用域中定义,那么该变量应该仍然可以在内部作用域中访问。实际上,在您的第一个示例中,当内部 _func 仅打印 x 时,它可以工作。

虽然 Python 不明显的是,这并不是确定变量范围的确切方式。 Python 在编译时分析哪些变量应该被认为是一个范围内的“本地”变量,具体取决于它们是否被分配到该范围内。在这种情况下,赋值包括扩充的赋值运算符。因此,当 Python 将第二个示例中的内部 _func 编译为字节码时,它会看到 x += 1 并确定 x 必须是 _func 的局部变量。当然,因为它的第一个赋值是一个扩充的赋值,所以本地没有 x 变量来扩充,你会得到 UnboundLocalError

另一种查看方式可能是这样写:

def _func():
    print x
    x = 2

这里又是因为_func 包含行x = 2 它会将x 视为该函数范围内的局部变量,而 视为外部函数中定义的x .所以print x 也应该导致UnboundLocalError

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

>>> 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'

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

这就是dis.dis 能够报告0 LOAD_FAST 0 (x) 的方式。稍后对于STORE_FAST 操作码也是如此。

为了理解 Python 中的变量作用域,没有必要知道这些,但了解幕后发生的事情可能会有所帮助。

正如在其他一些答案中已经提到的,Python 3 引入了 nonlocal 关键字,它可以防止在编译时将名称绑定到基于在本地范围内分配给该变量的本地变量。

【讨论】:

    【解决方案3】:

    您不能重新绑定在 Python 2 中关闭的变量(您可以在 Python 3 中使用 nonlocal)。如果我必须做你在 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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 2017-12-06
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2017-05-08
      • 2011-09-18
      相关资源
      最近更新 更多