以下代码适用于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中的一个错误。
答案 0 :(得分:5)
看来这是changed behavior in 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()
如果你在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运行,则不存在。