Python 3.4中的UnboundLocalError

Seá*_*yes 3 python

以下代码适用于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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

更新:

我通过修改我的代码使其工作如下:

except AttributeError as ex:
    # needed for Python 3 compatibility
    e = ex
    continue
Run Code Online (Sandbox Code Playgroud)

不知何故except...as实际上是e从本地范围中删除变量.好像它可能是我的Python中的一个错误.

mgi*_*son 5

看来这是python3.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()
Run Code Online (Sandbox Code Playgroud)

如果你在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
...
Run Code Online (Sandbox Code Playgroud)

特别注意DELETE_FAST操作码.如果使用python2.x运行,则不存在此问题.