19

I am trying to figure out the tradeoffs between different approaches of determining whether or not with object obj you can perform action do_stuff(). As I understand, there are three ways of determining if this is possible:

# Way 1
if isinstance(obj, Foo):
    obj.do_stuff()

# Way 2
if hasattr(obj, 'do_stuff'):
    obj.do_stuff()

# Way 3
try:
    obj.do_stuff()
except:
    print 'Do something else'

Which is the preferred method (and why)?

Felix
  • 1,764
  • 1
  • 16
  • 27
  • 2
    There's times for everything. Usually try/except (or don't even `except`, just let the exception percolate up the call stack) is the way to go, but there are exceptions (hohoho) to every rule. For example strings and lists share a lot of methods, but you often want to do different things with them in a recursive call. – roippi Jan 09 '14 at 16:03

3 Answers3

12

I believe that the last method is generally preferred by Python coders because of a motto taught in the Python community: "Easier to ask for forgiveness than permission" (EAFP).

In a nutshell, the motto means to avoid checking if you can do something before you do it. Instead, just run the operation. If it fails, handle it appropriately.

Also, the third method has the added advantage of making it clear that the operation should work.


With that said, you really should avoid using a bare except like that. Doing so will capture any/all exceptions, even the unrelated ones. Instead, it is best to capture exceptions specifically.

Here, you will want to capture for an AttributeError:

try:
    obj.do_stuff()   # Try to invoke do_stuff
except AttributeError:
    print 'Do something else'  # If unsuccessful, do something else
  • Is there any benefit to trying to access the method attribute, then calling it in the `else` clause, rather than simply trying to call it in the `try` clause? (I suppose you might want to distinguish between an `AttributeError` raised inside `do_stuff` from one generated by `do_stuff` not existing.) – chepner Jan 09 '14 at 16:28
  • @chepner - No, good catch. I was thinking of something else. –  Jan 09 '14 at 16:30
7

Checking with isinstance runs counter to the Python convention of using duck typing.

hasattr works fine, but is Look Before you Leap instead of the more Pythonic EAFP.

Your implementation of way 3 is dangerous, since it catches any and all errors, including those raised by the do_stuff method. You could go with the more precise:

try:
    _ds = obj.do_stuff
except AttributeError:
    print('Do something else')
else:
    _ds()

But in this case, I'd prefer way 2 despite the slight overhead - it's just way more readable.

phihag
  • 245,801
  • 63
  • 407
  • 443
  • I suspect, you meant "hasattr works fine, but is EAFP instead of the more Pythonic LBYL." to read the other way around "hasattr works fine, but is LBYL instead of the more Pythonic EAFP". – mloskot May 17 '15 at 13:27
  • 1
    @mloskot Thanks, fixed. – phihag May 17 '15 at 17:22
4

The correct answer is 'neither' hasattr delivers functionality however it is possibly the worst of all options.

We use the object oriented nature of python because it works. OO analysis is never accurate and often confuses however we use class hierarchies because we know they help people do better work faster. People grasp objects and a good object model helps coders change things more quickly and with less errors. The right code ends up clustered in the right places. The objects:

  • Can just be used without considering which implementation is present
  • Make it clear what needs to be changed and where
  • Isolate changes to some functionality from changes to some other functionality – you can fix X without fearing you will break Y

hasattr vs isinstance

Having to use isinstance or hasattr at all indicates the object model is broken or we are using it incorrectly. The right thing to do is to fix the object model or change how we are using it. These two constructs have the same effect and in the imperative ‘I need the code to do this’ sense they are equivalent. Structurally there is a huge difference. On meeting this method for the first time (or after some months of doing other things), isinstance conveys a wealth more information about what is actually going on and what else is possible. Hasattr does not ‘tell’ you anything.

A long history of development lead us away from FORTRAN and code with loads of ‘who am I’ switches. We choose to use objects because we know they help make the code easier to work with. By choosing hasattr we deliver functionality however nothing is fixed, the code is more broken than it was before we started. When adding or changing this functionality in the future we will have to deal with code that is unequally grouped and has at least two organising principles, some of it is where it ‘should be’ and the rest is randomly scattered in other places. There is nothing to make it cohere. This is not one bug but a minefield of potential mistakes scattered over any execution path that passes through your hasattr.

So if there is any choice, the order is:

  1. Use the object model or fix it or at least work out what is wrong with it and how to fix it
  2. Use isinstance
  3. Don’t use hasattr
ian
  • 41
  • 1
  • What do you mean by "fixing the object model", in this such case? Also, what good is "isinstance" here? You might want to check for example if "object" is a data structure with a shape attribute, and that would be a problem having to list a priori all structures for passing to "isinstance". At last what do you think about "getattr" and "try/except", which were considered good solutions by some? – Ando Jurai Jun 01 '17 at 07:16