2014-07-17 73 views
3

這一個讓我感到困惑。請看下面的Django模型 - 代表飼養員和籠在動物園,他們是負責打掃衛生:爲什麼getattr()在屬性不存在時拋出異常?

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') 

人們會想到這個打印「沒有動物園管理員」,因爲一個沒有被分配到該實例。相反,會發生異常:

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

回答

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" 
+0

你也可以通過捕獲Zookeeper.DoesNotExist來避免導入ObjectDoesNotExist。 – Bobort

5

該類最有可能定義了__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 
+0

好例子! – alecxe

+0

+1好解釋±0可怕的命名('o','O','O0','oO','Oo');) – msw

+1

@msw'O'和'C'鍵在字面上彼此相鄰, 我發誓! – metatoaster

相關問題