关于范围:在使用相同名称定义局部变量之前,python无法访问非局部变量

python can't access nonlocal variable before local variable is defined with same name

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Python nested functions variable scoping

我以前用过装饰,所以我很惊讶地发现我的代码中有一个错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def make_handler(name, panels):
    def get(self):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels = zip(ndb.get_multi(keys), panels)
        panels = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels]
        templates = {'panels': panels, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': get})

"""
Traceback (most recent call last):
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1536, in __call__
    rv = self.handle_exception(request, response, e)
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1530, in __call__
    rv = self.router.dispatch(request, response)
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File"C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File"C:\Users
obert\PycharmProjects\balmoral_doctors\main.py", line 35, in get
    keys = [ndb.Key('Panel', panel) for panel in panels]
UnboundLocalError: local variable 'panels' referenced before assignment
"""

我的解决方法是将panel改为panel2,而不是第一次使用:

1
2
3
4
5
6
7
8
9
def make_handler(name, panels):
    def get(self):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels2 = zip(ndb.get_multi(keys), panels)
        panels2 = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels2]
        templates = {'panels': panels2, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': get})

现在一切正常,我想知道为什么。

我想是这样的,但我不知道:

面板=拉链(…)

表示面板是局部变量。这意味着该函数不会在面板的外部范围中查找。

这是在运行get()函数之前完成的,而不是中途完成的?

我认为它首先从外部函数获取面板,然后当面板在内部函数中定义之后,它将从那时起使用新的局部面板变量。

我走对了吗?


对。

python的作用域规则表明函数定义了一个新的作用域级别,并且名称仅绑定到作用域级别中一个作用域级别的值—它是静态作用域(即,所有作用域都在编译时确定)。正如您所理解的,您试图通过从非局部声明中读取并写入局部变量来违反这一点。正如您所观察到的,解释器通过提升一个UnboundLocalError来强烈地反对这一点:它已经理解panels是一个局部变量(因为它不能同时是局部变量和非局部变量),但是您没有给名称赋值(绑定),所以它失败了。

更多技术细节

在python中决定跟踪字节码中变量在编译时的位置(具体来说,它位于tuple get.__code__.co_varnames中,用于局部变量),这意味着变量只能在特定范围内的单个范围级别中使用。在python 2.x中,不可能修改非局部变量;您要么对全局变量或非局部变量具有只读访问权,要么使用global语句对全局变量具有读写访问权,要么对局部变量具有读写访问权(默认值)。这就是它的设计方式(可能是为了性能和纯度)。在python 3中,引入了nonlocal语句,其效果与global相似,但只是用于中间范围。

在这种情况下,将修改后的变量绑定到其他名称是正确的解决方案。


你或多或少是正确的,你找到了正确的分辨率。您的问题相当于:

1
2
3
4
5
6
7
8
bars = range(10)

def foo():
  thing = [x for x in bars]
  bars = 'hello'

foo()
# UnboundLocalError: local variable 'bars' referenced before assignment

基本上在函数定义时,确定bars是局部范围。然后在函数运行时,你会遇到一个问题,那就是条形图没有被分配。


很多人没有意识到这一点,但是python实际上是静态作用域的。当python看到从一个裸名称(即不是某个对象的属性)中读取时,它可以完全通过编译时分析精确地确定该名称的读取位置。

如果在函数中曾经为某个名称赋值,那么该名称就是该函数1中的局部变量,它的作用域扩展到整个函数体,甚至扩展到赋值前的行。

如果在函数中未将名称赋给,则该名称是非局部变量。python可以检查周围任何def块的静态范围,以查看其中是否有局部变量。如果不是,则该名称必须是对模块全局或内置的引用(全局和内置之间的选择是动态解析的,因此可以用动态声明的全局来隐藏内置)。

我认为这样做主要是为了提高效率。这意味着在编译函数的字节码时,可以知道函数的局部变量集,并且可以将局部变量访问转换为简单的索引操作。否则,python将不得不执行字典查找来访问局部变量,这会比较慢。

因此,由于您的get函数包含一行形式为panels = ...的函数,那么panelsget主体整体的局部变量。对keys的赋值是在局部变量panels被赋值之前循环进行的。

1除非该名称被声明为globalnonlocal,但这仍然是静态已知的。


python编译器首先扫描函数内的整个代码,以确定局部变量集(作为参数传递或在函数内分配新的val)。这就是为什么在赋值之前不能使用在函数内部分配新值的变量。

如果编译器不这样做,那么就不可能为函数创建一个闭包。闭包需要引用所有自由变量或非局部变量。如果一个var对于函数的一部分是免费的,而对于另一部分是本地的……这就没有意义了:)


如果变量是在函数中赋值的,则假定它引用本地名称,除非您显式声明它global,或者在python 3.x中声明它nonlocal。如果声明为全局变量,则必须在模块的全局变量中定义该变量,而不是覆盖上述情况。您已经找到了一个python 2.x解决方案;另一个解决方案可能是将panels作为参数添加到get并使用functools.partial

1
2
3
4
5
6
7
8
9
def make_handler(name, panels):
    def get(self, panels):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels = zip(ndb.get_multi(keys), panels)
        panels = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels]
        templates = {'panels': panels, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': functools.partial(get, panels=panels)})

另请参见:python中的闭包、python非本地语句