Firstly, I'm not sure if my approach is proper, so I'm open to a variety of suggestions.
If try/except statements are frequently repeated in code, are there any good ways to shorten them or avoid fully writing them out?
try:
# Do similar thing
os.remove('/my/file')
except OSError, e:
# Same exception handing
pass
try:
# Do similar thing
os.chmod('/other/file', 0700)
except OSError, e:
#Same exception handling
pass
For example, for one line actions you could define a exception handling wrapper and then pass a lambda function:
def may_exist(func):
"Work with file which you are not sure if exists."""
try:
func()
except OSError, e:
# Same exception handling
pass
may_exist(lambda: os.remove('/my/file'))
may_exist(lambda: os.chmod('/other/file', 0700))
Does this 'solution' just make things less clear? Should I just fully write out all the try/except statements?
The best way to abstract exception handling is with a context manager:
from contextlib import contextmanager
#contextmanager
def common_handling():
try:
yield
finally:
# whatever your common handling is
then:
with common_handling():
os.remove('/my/file')
with common_handling():
os.chmod('/other/file', 0700)
This has the advantage that you can put full statements, and more than one of them, in each common_handling block.
Keep in mind though, your need to use the same handling over and over again feels a lot like over-handling exceptions. Are you sure you need to do this much?
It would probably be cleaner to make may_exist a decorator:
from functools import wraps
def may_exist(func):
#wraps(func):
def wrapper(*args, **kwds):
try:
return func(*args, **kwds)
except OSError:
pass
return wrapper
Then you can either do:
may_exist(os.remove)('/my/file')
may_exist(os.chmod)('/other/file', 0700)
for a one-off call, or:
remove_if_exists = may_exist(os.remove)
...
remove_if_exists('somefile')
if you use it a lot.
I think your generic solution is ok, but I wouldn't use those lambdas at the bottom. I'd recommend passing the function and arguments like this
def may_exist(func, *args):
"Work with file which you are not sure if exists."""
try:
func(args)
except OSError, e:
# Same exception handling
pass
may_exist(os.remove, '/my/file')
may_exist(os.chmod, '/other/file', '0700')
Would something like this work:
def may_exist(func, *func_args):
try:
func(*func_args)
except OSError as e:
pass
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 have a try/except where I repeat the except portion frequently in my code. This led me to believe that it would be better to separate the except portion into a function.
Below is my use case:
try:
...
except api.error.ReadError as e:
...
except api.error.APIConnectionError as e:
...
except Exception as e:
...
How would I go about separating this logic into a function so I can do something as simple as:
try:
...
except:
my_error_handling_function(e)
Just define the function:
def my_error_handling(e):
#Do stuff
...and pass in the exception object as the parameter:
try:
#some code
except Exception as e:
my_error_handling(e)
Using just a generic Exception type will allow you to have a single except clause and handle and test for different error types within your handling function.
In order to check for the name of the caught exception, you can get it by doing:
type(e).__name__
Which will print the name, such as ValueError, IOError, etc.
I would suggest refactoring your code so the try/except block is only present in a single location.
For instance, an API class with a send() method, seems like a reasonable candidate for containing the error handling logic you have described in your question.
Define your function:
def my_error_handling(e):
#Handle exception
And do what you're proposing:
try:
...
except Exception as e:
my_error_handling_function(e)
You can handle logic by getting the type of the exception 'e' within your function. See: python: How do I know what type of exception occurred?
If you don't like try-catch statement, you can use exception-decouple package and decorator.
from exception_decouple import redirect_exceptions
def my_error_handling(arg, e):
#Do stuff
#redirect_exceptions(my_error_handling, api.error.ReadError, api.error.APIConnectionError)
def foo(arg):
...
Some IO operations produce some set of errors. It is important, that it is not one exception, but set. So, we have set for socket errors, set for file io. How to handle group of exceptions without intersection for different io operations?
For example, OSError handles file io errors and some(?) Socket errors.
I have only one solution: wrap io operations with try-except and raise user-defined exception.
def foo():
try:
# some file io
except:
raise MyFileIOException(reason=sys.exc_info())
try:
# some socket io
except:
raise MySocketIOException(reason=sys.exc_info())
def bar():
try:
foo()
except MyFileIOException as exc:
# handle
except MySocketIOException as exc:
# handle
Is there some better and elegant solution?
I think, you have written a decent exception-handler. Moreover, it depends upon scenario. Say for eg. If you are making a function in library, then it's advisable not to introduce any application/domain specific exception types in the library function and simply raise raw/native exception types. When application code will use that function, it's its responsibility to declare its own set of exception types and wrap the exception generated in library into its own type.
eg. Library's code
def WriteToFile(strFile, strCnt):
objFile = open(strFile, "w")
objFile.write(strCnt)
objFile.close()
StudentRecord Application code
import library # import our library in which SaveStudent is present
import sys
def SaveStudent(objStudent):
try:
WriteToFile("studentRec.txt", str(objStudent))
except IOError as e:
raise StudentSaveFailedException, None, sys.exc_info()[2]
Now let this exception be handled in the application's Exception-handler component, which will meaningfully handle this exception and take necessary corrective actions.
Though I know that it's a naive explaination, but this is the strategy used in larger application, which uses several in-house and 3rd party libraries for proper exception handling.
Hope it helps you.
I'm ended with:
def decorator(f):
#functools.wraps(f)
#asyncio.coroutine
def wrapper(*args, **kwargs):
try:
return (yield from f(*args, **kwargs))
except asyncio.CancelledError:
raise
except:
raise CustomError(reason=sys.exc_info())
return wrapper