I have a try:finally block that must execute always (exception or not) unless a specific exception occurs. For the sake of argument let's say it's a ValueError, so I'm asking if I can implement:
try:
stuff()
except Exception as e:
if type(e) is ValueError: raise
#do important stuff
raise
#do important stuff
in a more elegant fashion to skip copy-pasting #importantstuff. If I ruled Python it would look something like:
try:
stuff()
finally except ValueError:
#do important stuff
Putting #importantstuff in a function is not an answer, but not possible is.
If you need finally to skip things in specific conditions, you'll need to use an explicit flag:
do_final_stuff = True
try:
# ...
except ValueError:
do_final_stuff = False
raise
finally:
if do_final_stuff:
# ...
You could also use a context manager here, to clean up afterwards. A context manager is passed the current active exception if there is one:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not ValueError:
# do cleanup
with MyContextManager():
# ...
Related
So I have multiple functions that perform different actions. Error handling is pretty much similar among functions, with slight variations though.
ErrorA and ErrorB are being handled in all functions. I would like to refactor this to avoid repeating the except clauses for ErrorA and B in every place. Is there a way in Python to get this? I do not want to change code behavior nor define nested try-except blocks. Your answers are very welcome!
def func_a():
try:
do_action_a()
except ErrorA:
handle_error_a()
except ErrorB:
handle_error_b()
except ErrorC:
handle_error_c()
def func_b():
try:
do_action_b()
except ErrorA:
handle_error_a()
except ErrorB:
handle_error_b()
except ErrorD:
handle_error_d()
def func_c():
try:
do_action_c()
except ErrorA:
handle_error_a()
except ErrorB:
handle_error_b()
except Exception:
handle_general_exception()
So, the most straightforwar way would be to refactor the handling of ErrorA and ErrorB into it's own function, something like:
def execute_with_a_b_handling(func, *args, **kwargs):
try:
return func(*args, **kwargs)
except ErrorA:
handle_error_a()
except ErrorB:
handle_error_b()
def func_a():
try:
execute_with_a_b_handling(do_action_a)
except ErrorC:
handle_error_c()
def func_b():
try:
execute_with_a_b_handling(do_action_b)
except ErrorD:
handle_error_d()
def func_c():
try:
execute_with_a_b_handling(do_action_c)
except Exception:
handle_general_exception()
Of course, with a better name.
Personally, I quite like using context managers in this situation. These are best used where there is a point in the code from which it is worth checking the error from, and then another point where the error should be removed.
from contextlib import contextmanager
global_errors = {}
#contextmanager
def error_handler_context(error, function):
# Code to acquire resource, e.g.:
global_errors[error] = function
try:
yield
finally:
# Code to release resource, e.g.:
del global_errors[error]
def handle_errors(function):
try:
function()
except Exception as e:
try:
global_errors[type(e)]()
except Exception:
raise e
def error_1():
print("here")
def value_error_raise():
raise ValueError("Test")
def exception_raise():
raise Exception("test Error")
with error_handler_context(ValueError, error_1):
handle_errors(value_error_raise)
handle_errors(exception_raise)
This is not a perfect solution, and there are definitely a lot of cases where this should not be used. So use caution.
For example, my function call make look like the following:
def parse(self, text):
...
return self.parse_helper(text)
#staticmethod
def parser_helper(text):
...
return normalize(text)
#staticmethod
def normalize(text):
...
try:
...
except:
raise ValueError('normalize failed.')
If the 'parse' is the function to be provided to users to call, if an exception occurs in normalize(), the whole program terminates. To avoid this, to let users decide what to do when exception occurs, do I have to and try ... except blocks into both 'parser_helper' and 'parse', and let use to use try...except when 'parse' is called?
What's the normal practice of handling this? If there are a few more layers of function calls embedded other than 3 as shown below, do I have to use try ... except block in each layer of function, in order to transfer the handling of exception to the end users at the very top?
The practice to pass a parameter to decide wether to raise or be silent regarding an exception, is something that exists, for example in os.makedirs
You could do
def parse(self, text, error_silent=False):
...
try:
return self.parse_helper(text)
except ValueError as e:
if error_silent:
return None
raise e
I have a situation where I want to do multiple things while handling an exception. Since I want to make this about the general case, I'll translate my specific case into some more general language.
When I have an exception in this piece of code, I want to:
Always perform a rollback-style operation
If it is an
application specific exception, I want to perform some logging and swallow the exception.
So I can think of two ways to solve it, both ugly:
# Method nested-try/except block
try:
try:
do_things()
except:
rollback()
raise
except SpecificException as err:
do_advanced_logging(err)
return
# Method Duplicate Code
try:
do_things()
except SpecificException as err:
rollback()
do_advanced_logging(err)
return
except:
rollback()
raise
Both will have the same behaviour.
I'm tending towards the nested try/except solution myself. While it might be slightly slower, I don't think the speed difference is relevant here - at the very least not for my specific case. Duplication of code is something I want to avoid also because my rollback() statement is slightly more involved that just a database rollback, even if it has the exact same purpose (it involves a web-API).
Is there a third option I haven't spotted that is better? Or is the duplicate code method better? Please note that the rollback() functionality is already factored out as much as possible, but still contains a function call and three arguments which includes a single hardcoded string. Since this string is unique, there's no reason to make it a named constant.
How about checking the exception instance type in code?
# Method .. No Duplicate Code
try:
do_things()
except Exception as e:
rollback()
if isinstance(e, SpecificException):
do_advanced_logging(e)
return
raise
how about putting the rollback in a finally clause? something like:
do_rollback = True
try:
do_things()
do_rollback = False
except SpecificException as err:
do_advanced_logging(err)
finally:
if do_rollback:
rollback()
an alternative is to use an else clause, which would let you do more in the non-exceptional case and not have exceptions all caught in the same place:
do_rollback = True
try:
do_things()
except SpecificException as err:
do_advanced_logging(err)
else:
record_success()
do_rollback = False
finally:
if do_rollback:
rollback()
is useful when record_success can raise a SpecificException, but you don't want to do_advanced_logging
You could write a context manager:
import random
class SpecificException(Exception):
pass
def do_things(wot=None):
print("in do_things, wot = {}".format(wot))
if wot:
raise wot("test")
def rollback():
print("rollback")
def do_advance_logging(exc_type, exc_val, traceback):
print("logging got {} ('{}')".format(exc_type, exc_val))
class rollback_on_error(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, traceback):
# always rollback
rollback()
# log and swallow specific exceptions
if exc_type and issubclass(exc_type, SpecificException):
do_advance_logging(exc_type, exc_val, traceback)
return True
# propagate other exceptions
return False
def test():
try:
with rollback_on_error():
do_things(ValueError)
except Exception as e:
print("expected ValueError, got '{}'".format(type(e)))
else:
print("oops, should have caught a ValueError")
try:
with rollback_on_error():
do_things(SpecificException)
except Exception as e:
print("oops, didn't expect exception '{}' here".format(e))
else:
print("ok, no exception")
try:
with rollback_on_error():
do_things(None)
except Exception as e:
print("oops, didn't expect exception '{}' here".format(e))
else:
print("ok, no exception")
if __name__ == "__main__":
test()
But unless you have dozen occurrences of this pattern, I'd rather stick to the very obvious and perfectly pythonic solutions - either nested exceptions handlers or explicit typecheck (isinstance) in the except clause.
I got a lot of code like this :
try:
# do a lot of stuff
except StuffError as e:
log.exception(e):
send_mail_to_admin()
raise e
For DRY, I wanted to refactor that into :
def post_mortem(log, e):
log.exception(e):
send_mail_to_admin()
# some other stuff
raise e
Then do :
try:
# do a lot of stuff
except StuffError as e:
post_mortem(log, e)
The trouble is now I don't get the proper stack trace, since the exception is raised from another file.
How can I get the same stack trace I would have had with the first code ?
Pass the exc_info() information also, as one of the parameters like this
post_mortem(log, e, sys.exc_info()[2])
And in the post_mortem
def post_mortem(log, e, traceBack):
traceback.print_tb(traceBack)
To get the entire stacktrace, you can do like shown in this example
import traceback, sys
def post_mortem(log, e, tb):
print "".join(traceback.format_list(traceback.extract_stack()[:-2]) + [traceback.format_tb(tb)[0]])
def throwError():
try:
raise NameError("I don't like your name")
except NameError as e:
post_mortem("", e, sys.exc_info()[2])
def callThrowError():
throwError()
callThrowError()
Output
File "/home/thefourtheye/Desktop/Test.py", line 15, in <module>
callThrowError()
File "/home/thefourtheye/Desktop/Test.py", line 13, in callThrowError
throwError()
File "/home/thefourtheye/Desktop/Test.py", line 8, in throwError
raise NameError("I don't like your name")
..you wanted it DRY? Try this then :)
class ReportExceptions(object):
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not None:
print("Sending mail to admin about {0!r}".format(exc_value))
And use it like this:
with ReportExceptions():
# your code here..
With a context manager, you don't have to care about re-raising exceptions, saving tracebacks or other things: the __exit__() method will be executed no matter what, passing you the necessary information. If you do nothing about it (eg. return True) the exception will just continue its way out..
Side note: you can instantiate the context manager only once, for example:
class ReportExceptions(object):
def __init__(self, email):
self.email = email
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not None:
print("Sending mail to {0!r} about {1!r}"
"".format(self.email, exc_value))
report_exceptions = ReportExceptions('foo#bar.com')
then:
with report_exceptions:
# ..your code here..
FYI: raising an exception with custom traceback
In case you really need to re-raise an exception, you can save the traceback and keep it for later..
try:
100 / 0
except ZeroDivisionError, e:
exc_info = sys.exc_info()
...later on...
raise exc_info[0], exc_info[1], exc_info[2]
(The syntax is actually raise expression, expression, expression, I couln't figure out a nicer way to use the tuple directly...)
Inspecting frames along the path
You can access tb attributes to inspect the execution frames along the path, for example the locals from the "most external" point would be tb.tb_frame.f_locals, the inner frame is at tb.tb_next.tb_frame, etc...
(you can also use the inspect module, see https://stackoverflow.com/a/10115462/148845)
I often find myself wanting to do something like this, I have something wrapped in try excepts like this
item= get_item()
try:
do_work(item)
except SomeError as err:
if err.code == 123:
do_something(item)
else:
# Actually I don't want to do something with this error code... I want to handle in 'except'
except:
put_back(item)
raise
Is there a way to raise into the except block below from the else? (a continue would be nice) I end up doing something like the following which isn't as clean
item= get_item()
try:
try:
do_work(item)
except SomeError as err:
if err.code == 123:
do_something(item)
else:
raise
except:
put_back(item)
raise
Is there anyway to do that?
If you are using a recent enough python version (2.5 and up), you should switch to using a context manager instead:
class WorkItemContextManager(object):
def __enter__(self):
self.item = get_item()
return self.item
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not None:
if exc_type is SomeError and exc_value.code == 123:
do_something(self.item)
return True # Exception handled
put_back(self.item)
Then:
with WorkItemContextManager() as item:
do_work(item)
The __exit__ method can return True if an exception has been handled; returning None will instead re-raise any exceptions raised in the with block.
If not, you are looking for a finally block instead:
item = get_item()
try:
do_work(item)
item = None
except SomeError as err:
if err.code == 123:
do_something(item)
item = None
finally:
if item is not None:
put_back(item)
The finally suite is guaranteed to be executed when the try: suite completes, or an exception has occurred. By setting item to None you basically tell the finally suite everything completed just fine, no need to put it back.
The finally handler takes over from your blanket except handler. If there has been an exception in do_work, item will not be set to None. If the SomeError handler doesn't catch the exception, or err.code is not 123, item will also not be set to None, and thus the put_back(item) method is executed.
My suggestion would be to create a function (or series of functions) that wraps the method throwing errors which you'd like to control. Something like...
def wrapper(arg):
try:
do_work(arg)
except SomeError as e:
if e.code == 123:
do_something(item)
# Other possible cleanup code
else:
raise
...then, when you want to call it...
try:
wrapper(arg)
except SomeError as e:
put_back(arg)
Context managers are excellent, but for some simple cases where you won't be reusing the logic anywhere else, they can be a bit heavy.
Instead of trying to have multiple except blocks, you can just test the exception inside a single except block:
item= get_item()
try:
do_work(item)
except Exception as err:
if isinstance(err, SomeError) and err.code == 123:
do_something(item)
else:
put_back(item)
raise
Note that this is pretty much what a context manager's __exit__ method ends up looking like, anyway.
Be wary that code that really belongs in finally doesn't end up here.
It's good to keep in mind what try-except flow is for, and one of their advantages is that they remove the need for status variables and status checks like
if not foo:
# do something
Also, an Exception class should represent a specific kind of error. If you need to make further decisions about the kind of error in an except block, it's a good sign that the class isn't specific enough to represent the program state. Your best bet is to subclass SomeError and only catch the subclass in the first except. Then other instances of SomeError will fall through to the second except block.