Python 3.4中的UnboundLocalError

3
以下代码可以在Python 2.7中运行,但在Python 3.4中会引发异常:
  File "/home/sean/dev/ving/meridian/venv/src/django-testmigrate/django_testmigrate/base.py", line 70, in __getattr__
    if e:
UnboundLocalError: local variable 'e' referenced before assignment

e 虽然在同一个函数的最上方被赋值了。我猜想在 Python 3 中有一些新的作用域规则,但我找不到任何参考资料。

以下是代码:

def __getattr__(self, name):
    e = None

    if not self._store:
        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))

    for state, scope in reversed(list(self._store.items())):
        try:
            val = getattr(scope, name)
        except AttributeError as e:
            continue
        else:
            e = None

            # get fresh instance
            if state != self._current_state and isinstance(val, models.Model):
                model_meta = val.__class__._meta
                model_class = self._current_state.get_model(model_meta.app_label, model_meta.model_name)
                val = model_class.objects.get(pk=val.pk)

            # add this value to the current scope
            setattr(self, name, val)
            break

    if e: # error raised here
        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))

    return val

更新:

我通过修改我的代码,使其正常工作,具体如下:

except AttributeError as ex:
    # needed for Python 3 compatibility
    e = ex
    continue

一些情况下,except...as 竟然会从局部作用域中删除变量 e。对我来说看起来像是Python的一个bug。

1
你可能在某个地方有缩进错误吗? - BrenBarn
哎呀,出问题了... @BrenBarn 这是一个 pastebin 链接,里面有我复现 OP 问题的代码:http://pastebin.com/pHHFyv0V 它可以在 Python2.7 上运行,但在 3.4 上不行。 - mgilson
我刚刚更新了描述,加入了新的信息。 - Seán Hayes
1
你为什么要写那样的代码?直接在except子句中引发新的AttributeError异常不就行了吗? - kindall
@kindall -- 嗯,这有点不同... 这似乎是试图推迟AttributeError直到以后。例如,尽可能设置这些属性,并在任何一个无法设置时引发异常。我同意这是一个有点奇怪的做法,但我想可能存在有效的理由? - mgilson
@kindall 我正在搜索一个对象集合中的变量,只有在任何地方都找不到它时才想引发异常。这是该项目的一个功能:https://github.com/greyside/django-testmigrate。 - Seán Hayes
1个回答

5

看起来这是Python 3.x中的变化。 具体来说:

当使用as target分配异常时,它会在except子句结束时被清除。

如果您再往后读一点,就会了解到这种更改的原因(基本上它可以防止当前堆栈帧中的引用循环,从而导致对象的生命周期比通常情况下更长)。

还描述了解决方法:

这意味着必须将异常分配给不同的名称,才能在except子句之后引用它。

这基本上就是您已经发现的内容。


请注意,如果我们查看反汇编源代码的操作码,就可以看到这一点。下面是一个简单的程序来演示:
def foo():
  e = None
  for _ in 'foobar':
    try:
      raise AttributeError
    except AttributeError as e:
      pass
    else:
      e = None
  if e:
    raise AttributeError

import dis
dis.dis(foo)

foo()

如果您在Python2.x和Python3.x上运行此代码,您会注意到一些差异。忽略3.x引发的UnboundLocalError,并仅查看反汇编源代码(对于py3.x运行),您会看到:
...
  7          50 POP_BLOCK
             51 POP_EXCEPT
             52 LOAD_CONST               0 (None)
        >>   55 LOAD_CONST               0 (None)
             58 STORE_FAST               0 (e)
             61 DELETE_FAST              0 (e)
             64 END_FINALLY
             65 JUMP_ABSOLUTE           13
        >>   68 END_FINALLY
...

特别注意DELETE_FAST操作码。如果您使用Python2.x,则不会出现此操作码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接