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.
Related
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 am trying to execute a loop while ignoring exceptions. I think pass or continue will allow me to ignore exceptions in a loop. Where should I put the pass or continue?
class KucoinAPIException(Exception):
"""Exception class to handle general API Exceptions
`code` values
`message` format
"""
def __init__(self, response):
self.code = ''
self.message = 'Unknown Error'
try:
json_res = response.json()
except ValueError:
self.message = response.content
pass
else:
if 'error' in json_res:
self.message = json_res['error']
if 'msg' in json_res:
self.message = json_res['msg']
if 'message' in json_res and json_res['message'] != 'No message available':
self.message += ' - {}'.format(json_res['message'])
if 'code' in json_res:
self.code = json_res['code']
if 'data' in json_res:
try:
self.message += " " + json.dumps(json_res['data'])
except ValueError:
pass
self.status_code = response.status_code
self.response = response
self.request = getattr(response, 'request', None)
def __str__(self):
return 'KucoinAPIException {}: {}'.format(self.code, self.message)
And this doesn't work:
from kucoin.exceptions import KucoinAPIException, KucoinRequestException, KucoinResolutionException
for i in range(10):
# Do kucoin stuff, which might raise an exception.
continue
Quick solution:
Catching the exceptions inside your loop.
for i in range(10):
try:
# Do kucoin stuff, which might raise an exception.
except Exception as e:
print(e)
pass
Adopting best practices:
Note that it is generally considered bad practice to catch all exceptions that inherit from Exception. Instead, determine which exceptions might be raised and handle those. In this case, you probably want to handle your Kucoin exceptions. (KucoinAPIException, KucoinResolutionException, and KucoinRequestException. In which case your loop should look like this:
for i in range(10):
try:
# Do kucoin stuff, which might raise an exception.
except (KucoinAPIException, KucoinRequestException, KucoinResolutionException) as e:
print(e)
pass
We can make the except clause less verbose by refactoring your custom exception hierarchy to inherit from a custom exception class. Say, KucoinException.
class KucoinException(Exception):
pass
class KucoinAPIException(KucoinException):
# ...
class KucoinRequestException(KucoinException):
# ...
class KucoinResolutionException(KucoinException):
# ...
And then your loop would look like this:
for i in range(10):
try:
# Do kucoin stuff, which might raise an exception.
except KucoinException as e:
print(e)
pass
Exception classes aren't designed to handle exceptions. They shouldn't actually have any logic in them. Exception classes essentially function like enums to allow us to quickly and easily differentiate between different types of exceptions.
The logic you have to either raise or ignore an exception should be in your main code flow, not in the exception itself.
You can use finally block to execute the block no matter what.
for i in range(10):
try:
#do something
except:
#catch exceptions
finally:
#do something no matter what
Is that is what you were looking for?
use try except in main block where KucoinAPIException is thrown
for i in range(10):
try:
# do kucoin stuff
# .
# .
# .
except:
pass
Since you mentioned ignoring exceptions I am assuming you would pass all exceptions. So no need to mention individual exceptions at except: line.
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):
...
So, let say I have 3 different calls called something, something1 and something2.
and right now, im calling it like
try:
something
something1
something2
except Keyerror as e:
print e
Note that in the above code, if something fails, something1 and something2 will not get executed and so on.
The wanted outcome is
try:
something
except KeyError as e:
print e
try:
something1
except KeyError as e:
print e
try:
something2
except KeyError as e:
print e
How can I achieve the above code without so many try except blocks.
EDIT:
So, the answer I chose as correct worked. But some of the others worked as well. I chose that because it was the simplist and I modified it a little.
Here is my solution based on the answer.
runs = [something, something1, something2]
for func in runs:
try:
func()
except Keyerror as e:
print e
You could try this, assuming you wrap things in functions:
for func in (something, something1, something2):
try:
func()
except Keyerror as e:
print e
Here's a little context manager I've used for similar situations:
from contextlib import contextmanager
#contextmanager
def ignoring(*exceptions):
try:
yield
except exceptions or Exception as e:
print e
with ignoring(KeyError):
something()
# you can also put it on the same line if it's just one statement
with ignoring(KeyError): something1()
with ignoring(KeyError): something2()
A Python 3 version could let you parameterize what to do when an exception occurs (the keyword-only arguments are needed here):
from contextlib import contextmanager
#contextmanager
def ignoring(*exceptions, action=print):
try:
yield
except exceptions or Exception as e:
callable(action) and action(e)
Then you could pass in some function other than print (such as a logger, assumed to be a function named log) or if you don't want anything, pass in None (since it checks to see if the action is callable):
with ignoring(KeyError, action=log): something()
I would go with something like this:
def safe_do(*statements):
for statement, args, kwargs in statements:
try:
statement(*args, **kwargs)
except KeyError as e:
print e
# usage:
safe_do(
(something1, [], {}),
(something2, [], {}),
)
But if you are expecting only one element to be missing in statements than why don't you if it?
if some_key1 in some_dict1:
something1
if some_key2 in some_dict2:
something2
much more readable and without any magic
Other possibility
def mydec(func):
def dec():
try:
func()
except KeyError as e:
print(e)
return dec
#mydec
def f1():
print('a')
#mydec
def f2():
print('b')
raise KeyError('Test')
f1()
f2()
This greatly depends on whether or not you're doing similar tasks, or very different tasks. For example, if your something lines are all very similar you could do the following:
def something(my_dict):
try:
d = my_dict['important_key'] # Just an example, since we
return d # don't know what you're really doing
except KeyError as e:
print e
something(dict1)
something(dict2)
something(dict3)
However, if your tasks are wildly different, this approach may not be applicable. To a certain degree you're asking "How do I write efficient code", and the answer to that depends on what code you're writing.
In python3, if you want to input a function with its args and kwargs, you can use the code below:
def safe_do(**statement):
try:
statement['func'](*statement['args'],**statement['kwargs'])
except Exception as e:
print(e)
print(statement['func'])
print(statement['args'])
print(statement['kwargs'])
def divide(a,b):
print(a/b)
safe_do(func=divide,args=[1,0],kwargs={})
In my colab notebook, I presented it.
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