97
if hasattr(obj, 'attribute'):
    # do somthing

vs

try:
    # access obj.attribute
except AttributeError, e:
    # deal with AttributeError

Which should be preferred and why?

Imran
  • 87,203
  • 23
  • 98
  • 131
  • 1
    Duplicate: http://stackoverflow.com/questions/598157/cheap-exception-handling-in-python, http://stackoverflow.com/questions/610883/how-to-know-if-an-object-has-an-attribute-in-python – Paolo Bergantino May 24 '09 at 05:48

13 Answers13

95

Any benches that illustrate difference in performance?

timeit it's your friend

$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "nonexistent")'
1000000 loops, best of 3: 1.87 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "a")'
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.a
except:
 pass'
1000000 loops, best of 3: 0.247 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.nonexistent
except:
 pass'
100000 loops, best of 3: 3.13 usec per loop
$

       |positive|negative
hasattr|  0.446 |  1.87 
try    |  0.247 |  3.13
Pierre.Vriens
  • 2,117
  • 75
  • 29
  • 42
ZeD
  • 1,009
  • 6
  • 2
  • 16
    +1 for providing interesting, tangible numbers. In fact, the "try" is efficient when it contains the common case (i.e. when a Python exception is really exceptional). – Eric O. Lebigot May 24 '09 at 07:31
  • 1
    I'm not sure how to interpret these results. Which is faster here, and by how much? – Stevoisiak Mar 27 '18 at 13:49
  • 2
    @StevenM.Vascellaro: If the attribute exists, `try` is about twice as fast as `hasattr()`. If it doesn't, `try` is about 1.5x slower than `hasattr()` (and both are substantially slower than if the attribute does exist). This is probably because, on the happy path, `try` hardly does anything (Python is already paying for the overhead of exceptions regardless of whether you use them), but `hasattr()` requires a name lookup and function call. On the unhappy path, they both have to do some exception handling and a `goto`, but `hasattr()` does it in C rather than Python bytecode. – Kevin Dec 06 '18 at 06:42
88

hasattr internally and rapidly performs the same task as the try/except block: it's a very specific, optimized, one-task tool and thus should be preferred, when applicable, to the very general-purpose alternative.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 9
    Except you still need the try/catch block to handle race conditions (if you are using threads). – Douglas Leeder May 24 '09 at 08:01
  • 1
    Or, the special case I just came across: a django OneToOneField with no value: hasattr(obj, field_name) returns False, but there is an attribute with field_name: it just raises a DoesNotExist error. – Matthew Schinckel Feb 25 '11 at 00:44
  • 3
    Note that `hasattr` will **catch all exceptions** in Python 2.x. See [my answer](http://stackoverflow.com/a/16186050/110204) for an example and the trivial workaround. – Martin Geisler Apr 24 '13 at 07:35
  • 5
    An interesting [comment](http://stackoverflow.com/a/21025020/1959808): `try` can convey that the operation *should* work. Though `try`'s intent is not always such, it is common, so it might be considered more readable. – 0 _ Jan 31 '14 at 20:25
33

There is a third, and often better, alternative:

attr = getattr(obj, 'attribute', None)
if attr is not None:
     print attr

Advantages:

  1. getattr does not have the bad exception-swallowing behavior pointed out by Martin Geiser - in old Pythons, hasattr will even swallow a KeyboardInterrupt.

  2. The normal reason you're checking if the object has an attribute is so that you can use the attribute, and this naturally leads in to it.

  3. The attribute is read off atomically, and is safe from other threads changing the object. (Though, if this is a major concern you might want to consider locking the object before accessing it.)

  4. It's shorter than try/finally and often shorter than hasattr.

  5. A broad except AttributeError block can catch other AttributeErrors than the one you're expecting, which can lead to confusing behaviour.

  6. Accessing an attribute is slower than accessing a local variable (especially if it's not a plain instance attribute). (Though, to be honest, micro-optimization in Python is often a fool's errand.)

One thing to be careful of is if you care about the case where obj.attribute is set to None, you'll need to use a different sentinel value.

Community
  • 1
  • 1
poolie
  • 9,289
  • 1
  • 47
  • 74
  • 1
    +1 - This is in league with dict.get('my_key', 'default_value') and should be more widely known about –  May 10 '14 at 23:15
  • 1
    Great for common use case where you want to check the existance and use the attribute with default value. – dsalaj Nov 23 '15 at 09:40
  • No need for `if attr is not None:`. Only `if attr:` is sufficient. – demberto Mar 04 '21 at 09:36
  • 1
    @demberto [`if attr` is sufficient only if you don't care to distinguish between the attribute being unset/None, or it being set to 0, `false`, or other values that are false in a boolean context](https://stackoverflow.com/q/7816363/243712) – poolie Mar 09 '21 at 14:28
  • ```In [1]: class Foo(): ...: def foo(self): ...: return getattr(self, 'x', False) ...: In [2]: f = Foo() In [3]: f.foo() Out[3]: False In [4]: f.x = 1 In [5]: f.foo() Out[5]: 1 ``` brilliant! – januszm Jun 09 '21 at 22:42
20

I almost always use hasattr: it's the correct choice for most cases.

The problematic case is when a class overrides __getattr__: hasattr will catch all exceptions instead of catching just AttributeError like you expect. In other words, the code below will print b: False even though it would be more appropriate to see a ValueError exception:

class X(object):
    def __getattr__(self, attr):
        if attr == 'a':
            return 123
        if attr == 'b':
            raise ValueError('important error from your database')
        raise AttributeError

x = X()
print 'a:', hasattr(x, 'a')
print 'b:', hasattr(x, 'b')
print 'c:', hasattr(x, 'c')

The important error has thus disappeared. This has been fixed in Python 3.2 (issue9666) where hasattr now only catches AttributeError.

An easy workaround is to write a utility function like this:

_notset = object()

def safehasattr(thing, attr):
    return getattr(thing, attr, _notset) is not _notset

This let's getattr deal with the situation and it can then raise the appropriate exception.

Martin Geisler
  • 72,968
  • 25
  • 171
  • 229
  • 2
    This was also [improved a bit](http://hg.python.org/cpython/rev/4c8375574aa9/) in Python2.6 so that `hasattr` will at least not catch `KeyboardInterrupt` etc. – poolie May 24 '13 at 03:04
  • Or, rather than `safehasattr`, just use `getattr` to copy the value in to a local variable if you're going to use it, which you almost always are. – poolie May 24 '13 at 03:04
  • @poolie That's nice, I didn't know that `hasattr` had been improved like that. – Martin Geisler May 24 '13 at 06:56
  • Yes, it is good. I didn't know that either until today when I was about to tell someone to avoid `hasattr`, and went to check. We had some funny bzr bugs where hasattr just swallowed ^C. – poolie May 24 '13 at 07:02
  • Was facing issue while upgrading 2.7 to 3.6. This answer help me to understand and resolving issue. – Kamesh Jungi Dec 11 '19 at 05:26
14

I would say it depends on whether your function may accept objects without the attribute by design, e.g. if you have two callers to the function, one providing an object with the attribute and the other providing an object without it.

If the only case where you'll get an object without the attribute is due to some error, I would recommend using the exceptions mechanism even though it may be slower, because I believe it is a cleaner design.

Bottom line: I think it's a design and readability issue rather than an efficiency issue.

Roee Adler
  • 33,434
  • 32
  • 105
  • 133
5

If not having the attribute is not an error condition, the exception handling variant has a problem: it would catch also AttributeErrors that might come internally when accessing obj.attribute (for instance because attribute is a property so that accessing it calls some code).

UncleZeiv
  • 18,272
  • 7
  • 49
  • 77
5

This subject was covered in the EuroPython 2016 talk Writing faster Python by Sebastian Witowski. Here's a reproduction of his slide with the performance summary. He also uses the terminology look before you leap in this discussion, worth mentioning here to tag that keyword.

If the attribute is actually missing then begging for forgiveness will be slower than asking for permissions. So as a rule of thumb you can use the ask for permission way if know that it is very likely that the attribute will be missing or other problems that you can predict. Otherwise if you expect code will result in most of the times readable code

3 PERMISSIONS OR FORGIVENESS?

# CASE 1 -- Attribute Exists
class Foo(object):
    hello = 'world'
foo = Foo()

if hasatter(foo, 'hello'):
    foo.hello
## 149ns ##

try:
    foo.hello
except AttributeError:
    pass
## 43.1 ns ##
## 3.5 times faster


# CASE 2 -- Attribute Absent
class Bar(object):
    pass
bar = Bar()

if hasattr(bar, 'hello'):
    bar.hello
## 428 ns ##

try:
    bar.hello
except AttributeError :
    pass
## 536 ns ##
## 25% slower
Community
  • 1
  • 1
jxramos
  • 7,356
  • 6
  • 57
  • 105
4

If it's just one attribute you're testing, I'd say use hasattr. However, if you're doing several accesses to attributes which may or may not exist then using a try block may save you some typing.

n8gray
  • 4,939
  • 3
  • 36
  • 33
3

I'd suggest option 2. Option 1 has a race condition if some other thread is adding or removing the attribute.

Also python has an Idiom, that EAFP ('easier to ask forgiveness than permission') is better than LBYL ('look before you leap').

Douglas Leeder
  • 52,368
  • 9
  • 94
  • 137
2

From a practical point of view, in most languages using a conditional will always be consderably faster than handling an exception.

If you're wanting to handle the case of an attribute not existing somewhere outside of the current function, the exception is the better way to go. An indicator that you may want to be using an exception instead of a conditional is that the conditional merely sets a flag and aborts the current operation, and something elsewhere checks this flag and takes action based on that.

That said, as Rax Olgud points out, communication with others is one important attribute of code, and what you want to say by saying "this is an exceptional situation" rather than "this is is something I expect to happen" may be more important.

cjs
  • 25,752
  • 9
  • 89
  • 101
  • +1 for insisting on the fact that "try" can be interpreted as "this is an exceptional situation", compared to the conditional test. :) – Eric O. Lebigot May 24 '09 at 07:30
1

The first.

Shorter is better. Exceptions should be exceptional.

Unknown
  • 45,913
  • 27
  • 138
  • 182
  • 5
    Exceptions are very common in Python -- there's one at the end of every `for` statement, and `hasattr` uses one, too. However, "shorter is better" (and "simpler is better"!) DO apply, so the simpler, shorter, more-specific hasattr is indeed preferable. – Alex Martelli May 24 '09 at 05:18
  • @Alex just because the Python parser transforms those statements to have 1 doesn't mean its very common. There's a reason why they made that syntactic sugar: so you aren't stuck with the cruftiness of typing the try except block. – Unknown May 24 '09 at 05:27
  • If the exception is exceptional, then "explicit is better", and the original poster's 2nd option is better, I'd say… – Eric O. Lebigot May 24 '09 at 07:35
0

At least when it is up to just what's going on in the program, leaving out the human part of readability, etc. (which is actually most of the time more imortant than performance (at least in this case - with that performance span), as Roee Adler and others pointed out).

Nevertheless looking at it from that perspective, it then becomes a matter of choosing between

try: getattr(obj, attr)
except: ...

and

try: obj.attr
except: ...

since hasattr just uses the first case to determine the result. Food for thought ;-)

Levite
  • 17,263
  • 8
  • 50
  • 50
0

Try/Except can be THE choice when hasattr() is a trigger.

If you're using @cached_property decorator provided by functools, there are cases where you don't want to use any attribute lookup to avoid executing the potentially expensive execution pre-emptively (if it does not exist as an attribute yet), but just try to to delete it. For instance if you need to make sure it hasn't been looked-up (and thus turned into an attribute) using other attributes that weren't ready - if it doesn't exist as an attribute, it means you can't del it and that's cool - and if it does exist you certainly want to del it.

if hasattr(obj,attr):
    # this will do a lookup and in case of @cached_property execute unless
    # the attribute already exists
    # not to speak about potential race conditions
    ...
    

try:
     del(self.attr)
except AttributeError:
     # ... we're happy
     # 
    
srn
  • 614
  • 3
  • 15