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?
4
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.
6
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.
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.
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).
2