7

可能重复:
Python 嵌套函数变量范围

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

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\Robert\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超出第一次使用:

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

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

这就是我猜会发生的事情,但我不知道:

面板 = zip(..)

意味着面板是一个局部变量。这意味着该功能不会在面板的外部范围内查找。

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

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

我在正确的轨道上吗?

4

5 回答 5

5

您或多或少是正确的,并且您找到了正确的解决方案。你的问题相当于这个:

bars = range(10)

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

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

在函数定义时,确定bars是局部作用域。然后在函数运行时,您会遇到未分配条形的问题。

于 2012-11-08T05:35:48.880 回答
5

是的。

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

更多技术细节

决定在 Python 中跟踪变量在编译时在字节码中的位置(对于这种情况,它位于get.__code__.co_varnames局部变量的元组中),这意味着变量只能在单个范围级别中使用一定范围。在 Python 2.x 中,不能修改非局部变量;您可以对全局或非局部变量进行只读访问,或者通过使用global语句对全局变量进行读写访问,或者对局部变量进行读写访问(默认设置)。这就是它的设计方式(可能是为了性能和纯度)。在 Python 3 中,nonlocal引入了该语句,其效果与 类似global,但适用于中间范围。

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

于 2012-11-08T05:36:27.040 回答
1

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

如果一个名字曾经在一个函数中被赋值,那么这个名字就是那个函数1中的一个局部变量,它的作用域延伸到整个函数体,甚至在赋值之前的行上

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

我相信这主要是为了有效地完成。这意味着在编译函数的字节码时可以知道函数的局部变量集合,并且可以将局部变量访问转换为简单的索引操作。否则 Python 将不得不执行字典查找来访问局部变量,这会更慢。

所以因为你的get函数包含一行形式的panels = ...,thenpanels是整个函数体中的一个局部变量get。赋值keys是在局部变量panels被赋值之前循环它。


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

于 2012-11-08T05:38:38.067 回答
0

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

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 非本地语句

于 2012-11-08T05:35:42.410 回答
0

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

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

于 2012-11-08T05:53:26.973 回答