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

时间:2012-11-08 05:20:33

标签: python scope decorator

  

可能重复:
  Python nested functions variable scoping

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

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

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

这就是我想的事情,但我不知道:

panels = zip(..)

表示面板是局部变量。这意味着该功能不会在面板的外观中查找。

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

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

我是在正确的轨道上吗?

5 个答案:

答案 0 :(得分:5)

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

更多技术细节

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

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

答案 1 :(得分:4)

您或多或少是正确的,并且您找到了正确的分辨率。你的问题等同于:

bars = range(10)

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

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

基本上在函数定义时,确定bars是本地范围。然后在函数运行时,你会遇到没有分配条形的问题。

答案 2 :(得分:1)

许多人没有意识到这一点,但Python实际上是静态范围。当Python看到一个裸名称的读取(即不是某个对象的属性)时,它可以确定精确地该名称读取将完全来自编译时分析。

如果在函数中指定了一个名称,那么该名称是该函数 1 中的局部变量,并且它的作用域扩展到函数体的整个部分,甚至超过作业前的行

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

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

因为get函数包含panels = ...形式的一行,所以panelsget整体中的局部变量。 keys的分配在分配之前循环遍历局部变量panels


1 除非该名称被声明为globalnonlocal,否则仍然是静态知名。

答案 3 :(得分:0)

如果在函数中指定了变量,则假定它引用本地名称,除非您明确声明它global或在Python 3.x中nonlocal。如果声明为global,则必须在模块的全局变量中定义变量,而不是覆盖上述情况。你找到了一个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)})

另请参阅:Closures in PythonPython nonlocal statement

答案 4 :(得分:0)

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

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