Reputation: 2589
What is the most elegant way to repeat something after it caused an exception in python?
I have something like this [pseudo code as an example]:
try:
do_some_database_stuff()
except DatabaseTimeoutException:
reconnect_to_database()
do_some_database_stuff() # just do it again
But imagine if I don't have a nice function but a lot of code instead. Duplicate code is not very nice.
So I think this is slightly better:
while True:
try:
do_some_database_stuff()
break
except DatabaseTimeoutException:
reconnect_to_database()
That's good enough if the exception really fixes the problem. If not I need a counter to prevent an indefinite loop:
i = 0
while i < 5:
try:
do_some_database_stuff()
break
except DatabaseTimeoutException:
reconnect_to_database()
i += 1
But then I don't really know if it worked so it's also:
while i <= 5:
try:
do_some_database_stuff()
break
except DatabaseTimeoutException:
if i != 5:
reconnect_to_database()
else:
raise DatabaseTimeoutException
i += 1
As you can see it starts to get very messy.
What is the most elegant way of expressing this logic?
Upvotes: 7
Views: 574
Reputation: 40853
I'm personally not a fan of the for-else construct. I don't think it's intutitive. First time I read it I thought it meant "do for loop (...), if iterable was empty then ...".
You should place your code inside a function. If do_some_database_stuff()
completes successfully then you can use the return statement to return early from the function.
eg.
def try_to_do_some_database_stuff():
for i in range(num_times):
try:
return do_some_database_stuff()
except DatabaseTimeoutException:
reconnect_to_database()
raise DatabaseTimeoutException
If you find yourself using this construct quite a lot then you can make it more generic.
def try_to_do(func, catch, times=2, on_exception=None):
for i in range(times):
try:
return func()
except catch:
if on_exception:
on_exception()
raise catch
try_to_do(do_some_database_stuff, catch=DatabaseTimeoutException, times=5,
on_exception=reconnect_to_database)
You can use functools.partial
if you need to pass arguments to your functions.
Upvotes: 1
Reputation: 249592
You can use a "for-else" loop:
for ii in range(5):
try:
do_some_database_stuff()
break
except DatabaseTimeoutException:
reconnect_to_database()
else:
raise DatabaseTimeoutException
Or, without:
for ii in range(5):
try:
do_some_database_stuff()
break
except DatabaseTimeoutException:
if ii == 4:
raise
reconnect_to_database()
Upvotes: 6