50

In Python, I often hear that it is better to "beg forgiveness" (exception catching) instead of "ask permission" (type/condition checking). In regards to enforcing duck typing in Python, is this

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

better or worse than

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

in terms of performance, readability, "pythonic", or some other important factor?

Martijn Pieters
  • 14,499
  • 10
  • 57
  • 58
darkfeline
  • 1,223
  • 1
  • 11
  • 12
  • 19
    there is a third option, don't do anything and treat any foo without a bar as a bug – jk. Nov 13 '12 at 09:37
  • I remember hearing that `hasattr` is implemented with that exact try/catch internally. Not certain if it's true... (it would act differently on properties, wouldn't it? Maybe I'm thinking of `getattr`..) – Izkata Nov 13 '12 at 14:09
  • @Izkata: The [implementation of `hasattr`](http://hg.python.org/cpython/file/bb21c800cf49/Python/bltinmodule.c#l871) does use the C-API equivalent of `getattr` (return `True` if successful, `False` if not), but handling exceptions in C is a lot faster. – Martijn Pieters Nov 13 '12 at 16:26
  • 2
    I've accepted martijn's answer, but I'd like to add that if you're trying to set an attribute, you should definitely consider using try/catch because it may be a property without a setter, in which case hasattr will be true, but it will still raise AttributeError. – darkfeline Nov 18 '12 at 20:58

4 Answers4

67

It really depends on how often you think the exception is going to be thrown.

Both approaches are, in my opinion, equally valid, at least in terms of readability and pythonic-ness. But if 90% of your objects do not have the attribute bar you'll notice a distinct performance difference between the two approaches:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

But if 90% of your objects do have the attribute, the tables have been turned:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

So, from a performance point of view, you need to pick the approach that works best for your circumstances.

In the end, some strategic use of the timeit module may be the most Pythonic thing you can do.

Martijn Pieters
  • 14,499
  • 10
  • 57
  • 58
  • 1
    Some weeks ago I asked this question: http://programmers.stackexchange.com/questions/161798/is-it-easier-to-write-robust-code-in-compiled-strictly-typed-languages There I asked whether in loosely typed languages you had to work extra doing type checks, and I was bombarded by people saying you hadn't. Know I see you have. – Tulains Córdova Nov 20 '12 at 15:26
  • @user1598390: When you define an API that *expects* a homogenous mix of types, you have to do some tests. Most of the time, you don't. This is specific area from which you cannot derive rules about paradigms as a whole, I'm afraid. – Martijn Pieters Nov 20 '12 at 16:10
  • Well, any serious system development involves defining an API. So I guess type strict languages are best for that because you have to code less since the compiler checks the types for you in compile time. – Tulains Córdova Nov 20 '12 at 18:06
  • It seems a bit inelegant to write `timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')` instead of `timeit.timeit(askforgiveness)` – Gareth Rees Apr 09 '13 at 15:59
  • 1
    @GarethRees: it establishes a pattern for the latter half of the answer where I pass in an argument to the function-under-test. – Martijn Pieters Apr 09 '13 at 16:13
  • 1
    Note that for `hasattr`, it actually does the C-api equivalent of a try-except under the hood anyway, since it turns out the only general way to determine whether an object has an attribute in Python is to try to access it. – user2357112 Aug 29 '14 at 22:54
12

In python you often get better performance doing things the Python way. With other languages, using exceptions for flow-control is generally regarded as a terrible idea because exceptions typically impose an extraordinary overhead. But because this technique is explicitly endorsed in Python, the interpreter is optimized for this type of code.

As with all performance questions, the only way to be certain is to profile your code. Write both versions and see which one runs faster. Though in my experience, the "Python way" is typically the fastest way.

tylerl
  • 4,850
  • 21
  • 32
3

Performance, I feel, is a secondary concern. If it arises, a profiler would help you focus on the real bottlenecks, which may or may not be how you treat possible illegal arguments.

Readability and simplicity, on the other hand, are always a prime concern. There are no hard rules here, just use your judgment.

This is a universal issue, but environment- or language-specific conventions are relevant. For example, in Python it's usually fine to simply use the attribute you expect and let a possible AttributeError reach the caller.

orip
  • 309
  • 1
  • 5
0

In terms of correctness, I think exception handling is the way to go (I sometimes use the hasattr() approach myself, though). The basic problem with relying on hasattr() is that it turns violations of code contracts into silent failures (this is a big problem in JavaScript, which doesn't throw on non-existing properties).

Joe
  • 111
  • 1
  • 3
    Your answer doesn't appear to add much beyond what has already been stated by others. Unlike other sites, Programmers expects answers that go into explaining the _why_ behind the answer. You touch upon a good point with the silent failure issue, but don't provide must justice to it. Reading the following may help: http://programmers.stackexchange.com/help/how-to-answer –  Apr 10 '15 at 15:48
  • The last answer I made was criticized for being too broad. I thought I'd try short and succinct. – Joe Apr 10 '15 at 22:16