Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Why is __getattr__ catching AttributeErrors?

I'm struggling with __getattr__. I have a complex recursive codebase, where it is important to let exceptions propagate.

class A(object):
    @property
    def a(self):
        raise AttributeError('lala')

    def __getattr__(self, name):     
        print('attr: ', name)
        return 1      

print(A().a)

Results in:

('attr: ', 'a')
1

Why this behaviour? Why is no exception thrown? This behaviour is not documented (__getattr__ documentation). getattr() could just use A.__dict__. Any thoughts?

like image 730
Dave Halter Avatar asked Jun 20 '12 09:06

Dave Halter


People also ask

What does __ Getattr __ do in Python?

__getattr__(self, name) Is an object method that is called if the object's properties are not found. This method should return the property value or throw AttributeError . Note that if the object property can be found through the normal mechanism, it will not be called.

Is Getattr slow?

Yes, compared to the direct conventional method of accessing an attribute of a given object, the performance of getattr() is slower.

How does Python handle attribute errors?

To avoid the AttributeError in Python code, a check should be performed before referencing an attribute on an object to ensure that it exists. The Python help() function can be used to find out all attributes and methods related to the object. To resolve the AttributeError , a try-except block can be used.

What is Getattribute in Python?

Python getattr() function is used to get the value of an object's attribute and if no attribute of that object is found, default value is returned.


2 Answers

Using __getattr__ and properties in the same class is dangerous, because it can lead to errors that are very difficult to debug.

If the getter of a property throws AttributeError, then the AttributeError is silently caught, and __getattr__ is called. Usually, this causes __getattr__ to fail with an exception, but if you are extremely unlucky, it doesn't, and you won't even be able to easily trace the problem back to __getattr__.

Unless your property getter is trivial, you can never be 100% sure it won't throw AttributeError. The exception may be thrown several levels deep.

Here is what you could do:

  1. Avoid using properties and __getattr__ in the same class.
  2. Add a try ... except block to all property getters that are not trivial
  3. Keep property getters simple, so you know they won't throw AttributeError
  4. Write your own version of the @property decorator, which catches AttributeError and re-throws it as RuntimeError.

See also http://blog.devork.be/2011/06/using-getattr-and-property_17.html

EDIT: In case anyone is considering solution 4 (which I don't recommend), it can be done like this:

def property_(f):
    def getter(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except AttributeError as e:
            raise RuntimeError, "Wrapped AttributeError: " + str(e), sys.exc_info()[2]

    return property(getter)

Then use @property_ instead of @property in classes that override __getattr__.

like image 89
Florian Winter Avatar answered Sep 29 '22 06:09

Florian Winter


I just changed the code to

class A(object):
    @property
    def a(self):
        print "trying property..."
        raise AttributeError('lala')
    def __getattr__(self, name):     
        print('attr: ', name)
        return 1      

print(A().a)

and, as we see, indeed the property is tried first. But as it claims not to be there (by raising AttributeError), __getattr__() is called as "last resort".

It is not documented clearly, but can maybe be counted under "Called when an attribute lookup has not found the attribute in the usual places".

like image 32
glglgl Avatar answered Sep 29 '22 05:09

glglgl