Scott David Daniels wrote:
Brian van den Broek wrote:
... STeVe stressed that the try/except solution is only really
appropriate for cases where the failure to have the variable defined
is quite rare.
Beware: C++ and Java have an immense overhead for exceptions. Python
has a very lightweight exception mechanism. You should _very_seldom_
choose exceptions or not on the basis of performance without measuring
the actual use; you are sure to be surprised.
I'll just point out that I wasn't suggesting that try/except should be
used as an optimization, but that it should be used when *exceptional*
behavior is encountered. Using try/except for non-exceptional behavior
can be confusing for readers of your code who assume the normal
semantics. Just to make sure my point is clear, a great case for
try/except is with dictionaries, e.g.
try:
v = d[k]
except KeyError:
...
In this case, the key not being in the dictionary clearly makes sense as
exceptional behavior because there's no reason to have a dictionary if
there's nothing in it. Similarly, try/excepts are great for dealing
with duck typing issues, e.g.:
def f(mapping)
try:
itervalues = mapping.itervalues
except AttributeError:
values = (mapping[k] for k in mapping)
else:
values = itervalues()
...
Again, the point is that the AttributeError is the *exceptional*
behavior; f expects a mapping with an itervalues method, and if it
receives an object that doesn't have one, it has to deal with the
exceptional case of replacing that method.
Anyway, I hope that clarifies my intentions. Use try/except when it
makes sense to talk about something as being *exceptional* behavior. If
it doesn't, you should probably use if/else.
STeVe
P.S. That said, there *are* performance differences. Here's a test that
shows how try/except can be more costly the more often the except clause
is reached:
----------------------------------------------------------------------
import timeit
def ifelse(mapping, key):
if key in mapping:
return mapping[key]
else:
return None
def tryexcept(mapping, key):
try:
return mapping[key]
except KeyError:
return None
setup_code = """\
from __main__ import %s as func
mapping = %s
keys = %s
"""
test_code = '[func(mapping, key) for key in keys]'
def get_time(func, mapping, keys):
return timeit.Timer(test_code, setup_code % (
func.__name__, mapping, keys)).timeit(1000)
if __name__ == '__main__':
N = 1000
mapping = dict((i, i**2) for i in xrange(N))
for i in [0, 1, 2, 4, 8]:
number_missing = i*N/100
keys = range(number_missing, N + number_missing)
ifelse_time, tryexcept_time = (get_time(func, mapping, keys)
for func in [ifelse, tryexcept])
result = ['%3i%%' % (100*number_missing/N)]
for time, func in sorted([(ifelse_time, ifelse),
(tryexcept_time, tryexcept)]):
result.extend(['%.4f' % time, '%10s' % func.__name__])
print '\t'.join(result)
----------------------------------------------------------------------
And the results I get from running it:
[D:\Steve]$ test.py
0% 0.8467 tryexcept 0.9415 ifelse
1% 0.9374 tryexcept 0.9430 ifelse
2% 0.9499 ifelse 1.0375 tryexcept
4% 0.9580 ifelse 1.1576 tryexcept
8% 0.9333 ifelse 1.4187 tryexcept
Thus try/except is better when almost all of the keys can be found in
the dict, but as soon as even 2% of the keys cannot, if/else is the more
efficient solution. This is toy data, so obviously YMMV. But if you
use try/except for a very frequent occurrence instead of an exceptional
one, you may notice a performance hit.