I want a more detailed error log.
Specifically, I'd like to access the f_locals of an exception raising frame from inside an except clause.
def dumb_raiser(string: str):
if not isinstance(string, str):
raise ValueError("Yup")
try:
dumb_raiser(1)
except ValueError as ex:
# grab f_locals from frame and log here
pass
How would you access the necessary frame to do this?
One approach is to simply tack on the function's locals() to the exception when you raise it:
def dumb_raiser(string: str):
if not isinstance(string, str):
raise ValueError("Yup", locals())
try:
dumb_raiser(1)
except ValueError as ex:
print(ex.args[1])
This prints: {'string': 1}, as you'd expect.
I don't know if this is the best way (you could also examine the stackframes here) but the idea is to just use a decorator.
def derp(f):
def wrapped(*args, **kwargs):
try:
f(*args, **kwargs)
except Exception as ex:
raise ValueError("Failed args %s" % args)
return wrapped
def dumb_raiser(string):
if not isinstance(string, str):
raise ValueError("Yup")
try:
dumb_raiser = derp(dumb_raiser)
dumb_raiser(1)
except ValueError as ex:
print ex.message
Obviously you could do whatever you want to in the decorated exception handling. I just re-raised a ValueError with a simple string to show the passed arguments.
You can pull from Sentry's Python client, which captures stack locals with f_locals.
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.
I am trying to raise an exception inside the exception, without getting an error message of the outer exception and the traceback and print the hey only. However, I am a quite stuck here if I put a string instead of k or m. Any ideas?
The output, which I would need without the traceback
def division(k, m):
try:
k / m
except TypeError:
raise ValueError ('hey') from None
return k / m
I'm not exactly sure what you are trying to achieve here, but why don't you just print the string "hey" when the TypeError is raised? Like this:
def division(k, m):
try:
k / m
except TypeError:
print("hey")
return None
return k / m
If you need to raise the ValueError for any reason, then you can catch the ValueError where you call the method, I guess:
def division(k, m):
try:
k / m
except TypeError:
raise ValueError
return k / m
try:
division("not_an_int", "could_be_an_int")
except ValueError:
print("hey")
From what I understand, You want a custom exception with a custom message format.
class CustomException(Exception):
def __init__(self, message):
self.message = message # You can also set it by default so you don't need to input any message
super().__init__(self.message)
def __eq__(self, other):
return self.message == other.message
def __str__(self):
return f'{self.message}' # In your case, just to display message.
# return f'Error message: {self.message}'
def division(k, m):
try:
k / m
except TypeError:
raise CustomException(message="hey")
return k / m
try:
division("not_an_int", "could_be_an_int")
except ValueError as e:
print(e)
It is not entirely clear what you are trying to achieve here. You don't get the error message because you raise the exception but because you don't catch it again. If you don't want the error message, you need to catch the exception. Then, if you just want to print the message 'hey' you can get it from the exception args.
try:
division('a', 2)
except ValueError as e:
print(e.args[0])
The raise ... from None already got rid of the first exception, so you are only seeing the ValueError you raise in the except block in division, but if an exception makes it all the way out to the command line, you have to be informed somehow, and Python will do so by printing the error message.
Now, you can change the default behaviour for uncaught exceptions if you want to. This, for example, will print the args[0] for all exceptions you do not explicitly catch.
import sys
def handle_exception(exc_type, exc_value, exc_traceback):
print('handler:', exc_value.args[0])
sys.excepthook = handle_exception
The sys.excepthook is a function Python will call for an uncaught exception. So if you do
try:
division('a', 2)
except ValueError as e:
print('caught:', e)
division('a', 2) # not caught
the first division exception is caught, and the handler isn't invoked, and the second isn't caught and the handler is used (and will just print the message 'hey').
It is not really a great idea to change the way all uncaught exceptions are handled, though. You probably want to handle only your own and use the default behaviour for anything else.
But, as I said, it is not entirely clear to me what you are trying to achieve, so all of the above might be entirely unrelated to the question.
currently I have some code like this
try:
somecode
except Exception as Error:
fallbackcode
Now i want to add another fallback to the fallbackcode
Whats is the best practice to make a nested try/catch in python?
try:
somecode
except Exception as Error:
try:
fallbackcode
except Exception as Error:
nextfallbackcode
produces intendation errors
You should be able to do nested try/except blocks exactly how you have it implemented in your question.
Here's another example:
import os
def touch_file(file_to_touch):
"""
This method accepts a file path as a parameter and
returns true/false if it successfully 'touched' the file
:param '/path/to/file/'
:return: True/False
"""
file_touched = True
try:
os.utime(file_to_touch, None)
except EnvironmentError:
try:
open(file_to_touch, 'a').close()
except EnvironmentError:
file_touched = False
return file_touched
You can rewrite the logic using a loop provided all your callbacks and fallbacks have same API interface
for code in [somecode, fallbackcode, nextfallbackcode]:
try:
code(*args, **kwargs)
break
except Exception as Error:
continue
else:
raise HardException
This would be the preferred way instead of multiple level of nested exception blocks.
you can use functions to handle errors :
def func0():
try:
somecode
except:
other_func()
def func1():
try:
somecode
except:
func0()
You could have change it like this:
try:
try:
somecode
except Exception as Error:
fallbackcode
except Exception as Error:
nextfallbackcode
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 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)