This one has me baffled. Consider the following Django models - representing zookeepers and the cages at the zoo that they are responsible for cleaning:
class Zookeeper(moodels.Model):
name = models.CharField(max_length=40)
class Cage(models.Model):
zookeeper = models.ForeignKey(Zookeeper)
Now suppose I want to connect a receiver to the Cage
's post_init
signal:
@receiver(models.signals.post_init, sender=Cage)
def on_cage_init(instance, **kwargs):
print instance.zookeeper
As expected, this raises an exception since the Cage
has not yet been assigned to a Zookeeper
. Consider the following modification to the body of the receiver:
print getattr(instance, 'zookeeper', 'No Zookeeper')
One would expect this to print "No Zookeeper" since one has not been assigned to the instance. Instead, an exception is raised:
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.
Why is it raising an exception? Isn't getattr()
supposed to return the provided default value if the attribute does not exist? I can prove that the attribute does not exist with:
print hasattr(instance, 'zookeeper')
...which prints False
.
Most likely that class has __getattribute__
defined. See this example:
>>> 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
Note how getattr
essentially calls o.__getattribute__
internally, and if that raises a generic exception, it will just fail with that exception.
However, if it was properly define to raise AttributeError
, getattr
will catch that properly.
>>> class O(object):
... def __getattribute__(self, name):
... raise AttributeError("can't get attribute")
...
>>> o = O()
>>> getattr(o, 'test', 'nothing')
'nothing'
So this could be considered as a bug in the definition of the DoesNotExist
exception where it does not correctly inherit from AttributeError
as it should have.
A more complete example to demonstrate all of the above:
>>> 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
Of course, all that above doesn't exactly help you in working around that bug. Rather than waiting for that bug to be resolved, just implement your getattr
that traps that exception (or any other exceptions that you might be expecting). Something like this might work:
def safe_getattr(obj, name, default):
try:
return getattr(obj, name, default)
except Exception: # or your specific exceptions
return default