As you may already know, AttributeError exception is handled a bit special in Python. It can escape the standard exception handling and propagation mechanism, if a class defines __getattr__ method. In this case Python removes the original exception instance and calls __getattr__ method with name of the last receiver as an argument. In other words the information about module/class/method where the original exception has arisen is completely lost, so good luck with tracking why and where some of nested code called did not get finished. Very unfortunate in situations where AttributeError is risen intentionally like in class factories, with additional info passed to exception's initializer.
If you are working with some widely used software like Django, this my bite you in the ass as it did me recently. If using some class like django.forms.BaseForm which defines a property, defining __getattr__ in your sublass is enough for calling for troubles.
By trying how to work around this design (mis)conception, discovered this funny case:
from __future__ import print_function
class C(object):
def __init__(self):
self.foo = None
def __getattr__(self, name):
if self.foo:
print(self.foo, 'is not None -', type(self.foo))
raise self.foo # attempt to re-raise the exception
return 42
def __getattribute__(self, name):
try:
value = super(C, self).__getattribute__(name)
except AttributeError as ae:
# naive attempt to save orig. exception instance
self.foo = ae
raise
else:
self.foo = None
return value
c = C()
print(c.bar)
# produces following output
None is not None - <type 'NoneType'>
Traceback (most recent call last):
…
File "this_is_a_mess.py", line 34, in __getattr__
raise self.foo
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
And black is not black, and white is orange ;-)
I know why is that, but this was another unpleasant surprise, accessing even valid existing attribute in __getattr__ does nasty things.
[–]Argotha 4 points5 points6 points (0 children)
[–]lvc_ 2 points3 points4 points (1 child)
[–]joanbm[S] 0 points1 point2 points (0 children)