为什么getattr()在属性不存在时抛出异常?

时间:2014-07-17 04:02:06

标签: python django django-models getattr

这个令我感到困惑。考虑以下Django模型 - 代表动物园管理员和他们负责清洁的动物园笼子:

class Zookeeper(moodels.Model):
    name = models.CharField(max_length=40)

class Cage(models.Model):
    zookeeper = models.ForeignKey(Zookeeper)

现在假设我想将接收器连接到Cage的{​​{1}}信号:

post_init

正如预期的那样,这会引发异常,因为@receiver(models.signals.post_init, sender=Cage) def on_cage_init(instance, **kwargs): print instance.zookeeper 尚未分配给Cage。考虑对接收者身体的以下修改:

Zookeeper

可以预期这会打印“No Zookeeper”,因为尚未将其分配给实例。相反,会引发异常:

print getattr(instance, 'zookeeper', 'No Zookeeper')

为什么会引发异常?如果该属性不存在,Traceback (most recent call last): File "../zoo/models.py", line 185, in on_cage_init print getattr(instance, 'zookeeper', 'No Zookeeper') File "/usr/local/lib/python2.7/dist-packages/django/db/models/fields/related.py", line 324, in __get__ "%s has no %s." % (self.field.model.__name__, self.field.name)) DoesNotExist: Cage has no zookeeper. 是否应该返回提供的默认值?我可以证明该属性不存在:

getattr()

...打印print hasattr(instance, 'zookeeper')

2 个答案:

答案 0 :(得分:6)

该类很可能已定义__getattribute__。见这个例子:

>>> class O(object):
...     def __getattribute__(self, name):
...         raise Exception("can't get attribute")
...
>>> o = O()
>>> getattr(o, 'test', 'nothing')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __getattribute__
Exception: can't get attribute

请注意getattr在内部如何实质上调用o.__getattribute__,如果这会引发一般异常,那么它只会因该异常而失败。

但是,如果正确定义以引发AttributeError,则getattr会正确捕获。

>>> class O(object):
...     def __getattribute__(self, name):
...         raise AttributeError("can't get attribute")
...
>>> o = O()
>>> getattr(o, 'test', 'nothing')
'nothing'

因此,这可能会被视为DoesNotExist例外定义中的一个错误,它不能正确地从AttributeError继承。

一个更完整的例子来演示以上所有内容:

>>> class O(object):
...     def __getattribute__(self, name):
...         if name == 'test':
...             return 'good value'
...         elif name == 'bad':
...             raise Exception("don't raise this")
...         else:
...             raise DoesNotExist()
...
>>> class DoesNotExist(AttributeError):
...     pass
...
>>> o = O()
>>> getattr(o, 'test', 'nothing')
'good value'
>>> getattr(o, 'something', 'nothing')
'nothing'
>>> getattr(o, 'bad', 'nothing')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __getattribute__
Exception: don't raise this

当然,上述所有内容并不能完全帮助您解决该问题。而不是等待解决该错误,只需实现陷阱该异常的getattr(或您可能期望的任何其他异常)。这样的事情可能有用:

def safe_getattr(obj, name, default):
    try:
        return getattr(obj, name, default)
    except Exception:  # or your specific exceptions
        return default

答案 1 :(得分:5)

@ metatoaster的解释非常好,这基本上就是发生了什么。请参阅__get__魔法定义here

作为解决方案,我会应用"Easier to ask for forgiveness than permission"原则。尝试获取属性并捕获特定的异常:

from django.core.exceptions import ObjectDoesNotExist

try:
    print instance.zookeeper
except ObjectDoesNotExist:
    print "No zookeeper"