Python hasattr vs getattr

24,572

Solution 1

The documentation does not encourage, the documentation just states the obvious. The hasattr is implemented as such, and throwing an AttributeError from a property getter can make it look like the attribute does not exist. This is an important detail, and that is why it is explicitly stated in the documentation. Consider for example this code:

class Spam(object):
    sausages = False

    @property
    def eggs(self):
        if self.sausages:
            return 42
        raise AttributeError("No eggs without sausages")

    @property
    def invalid(self):
        return self.foobar


spam = Spam()
print(hasattr(Spam, 'eggs'))

print(hasattr(spam, 'eggs'))

spam.sausages = True
print(hasattr(spam, 'eggs'))

print(hasattr(spam, 'invalid'))

The result is

True
False
True
False

That is the Spam class has a property descriptor for eggs, but since the getter raises AttributeError if not self.sausages, then the instance of that class does not "hasattr" eggs.

Other than that, use hasattr only when you don't need the value; if you need the value, use getattr with 2 arguments and catch the exception, or 3 arguments, the third being a sensible default value.

The results using getattr() (2.7.9):

>>> spam = Spam()
>>> print(getattr(Spam, 'eggs'))
<property object at 0x01E2A570>
>>> print(getattr(spam, 'eggs'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in eggs
AttributeError: No eggs without sausages
>>> spam.sausages = True
>>> print(getattr(spam, 'eggs'))
42
>>> print(getattr(spam, 'invalid'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in invalid
AttributeError: 'Spam' object has no attribute 'invalid'
>>>

Solution 2

Seems that hasattr has a problem with swallowing exceptions (at least in Python 2.7), so probably is better to stay away from it until it's fixed.

Take, for instance, the following code:

>>> class Foo(object):
...     @property
...     def my_attr(self):
...         raise ValueError('nope, nope, nope')
...
>>> bar = Foo()
>>> bar.my_attr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_attr
ValueError: nope, nope, nope
>>> hasattr(Foo, 'my_attr')
True
>>> hasattr(bar, 'my_attr')
False
>>> getattr(bar, 'my_attr', None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_attr
ValueError: nope, nope, nope
>>>
Share:
24,572
raulcumplido
Author by

raulcumplido

Updated on October 25, 2020

Comments

  • raulcumplido
    raulcumplido over 3 years

    I have been reading lately some tweets and the python documentation about hasattr and it says:

    hasattr(object, name)

    The arguments are an object and a string. The result is True if the string is the name of >> one of the object’s attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an AttributeError or not.)

    There is a motto in Python that says that is Easier to ask for forgiveness than permission where I usually agree.

    I tried to do a performance test in this case with a really simple python code:

    import timeit
    definition="""\
    class A(object):
        a = 1
    a = A()
    """
    
    stm="""\
    hasattr(a, 'a')
    """
    print timeit.timeit(stmt=stm, setup=definition, number=10000000)
    
    stm="""\
    getattr(a, 'a')
    """
    print timeit.timeit(stmt=stm, setup=definition, number=10000000)
    

    With the results:

    $ python test.py
    hasattr(a, 'a')
    1.26515984535
    
    getattr(a, 'a')
    1.32518696785
    

    I´ve tried also what happens if the attribute doesn´t exists and the differences between getattr and hasattr are bigger. So what I´ve seen so far is that getattr is slower than hasattr, but in the documentation it says that it calls getattr.

    I´ve searched the CPython implementation of hasattr and getattr and it seems that both call the next function:

    v = PyObject_GetAttr(v, name);
    

    but there is more boilerplate in getattr than in hasattr that probably makes it slower.

    Does anyone knows why in the documentation we say that hasattr calls getattr and we seem to encourage the users to use getattr instead of hasattr when it really isn´t due to performance? Is just because it is more pythonic?

    Maybe I am doing something wrong in my test :)

    Thanks,

    Raúl

  • binaryfunt
    binaryfunt almost 3 years
    In other words, the documentation is telling a white lie