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

2024-05-14 18:28:23 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

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

现在假设我要将接收器连接到Cagepost_init信号:

@receiver(models.signals.post_init, sender=Cage)
def on_cage_init(instance, **kwargs):
    print instance.zookeeper

正如预期的那样,这会引发异常,因为Cage尚未分配给Zookeeper。考虑对接收器主体进行以下修改:

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

有人会希望这会打印“没有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')

…打印False


Tags: instancenamemodelinitonmodelszookeeperpost
2条回答

很可能该类定义了__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

@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"

相关问题 更多 >

    热门问题