Python exceptions - catching all exceptions but the one expected - python

I am working on a simple automation script in Python, which could throw exceptions in various spots. In each of them I would like to log a specific message and exit the program. In order to do that, I raise SystemExit after catching the exception and handling it (performing specific logging operations and such).
In the top-level calling of main, I do the following:
if __name__ == "__main__":
try:
main()
except SystemExit: # handled exception
sys.exit(1)
except: # any unhandled exception
logging.error('Unexpected error: ', exc_info=True)
sys.exit(2)
However, using a bare except is something frowned upon. Is using an "exception tree" where I use a bare except to specify "anything but the exception that I've handled" a nonstandard way? Is there a better way to achieve this? I would still like to log these unhandled exceptions, even if they were not handled.
Edit: SystemExit is raised to note that an exception has been handled - no matter what the exception is in my case, I always want to stop running the scripts as any failure should result in an absolute failure.
The main reason I'm asking this is that PEP8 seems to consider using a bare except as an error, and even though I could use except BaseException, it should be just a syntactic difference. Is one way more standard than the other or is there another standard route of achieving this?

Bare exceptions trap things you do not want to trap, such as GeneratorExit. Do it this way:
except Exception as details:
logging.error('Unexpected error: {0}'.format(details))

The main issue with a bare except is that it can catch things like SystemExit and KeyboardInterrupt which are not standard 'code' errors and shouldn't usually be handled in the same way as an exception generated by your code. Using the Exception class doesn't cover those cases as they do not inherit from it, so it is more than a syntax difference.
https://docs.python.org/2/howto/doanddont.html#except
https://docs.python.org/3.1/howto/doanddont.html#except
If you want to handle those specific cases, then it is better to do so explicitly as you have done for SystemExit.

This worked for me:
try:
<code>
raise Exception("my error")
except Exception as e:
raise e
If my error occurs then the error message "my errror" is seen. If an unknown exception occurs then it displays the default exception handler's text. In either case an exception is raised and the script is halted.

Related

exit(1) in signal handler just gets caught as SystemExit as there is nothing to it

I have an application that looks like this:
while True:
try:
self.try_to_read_usb_device()
break
except:
time.sleep(1)
I also have an SIGALRM handler which is supposed to exit the program in case it got stuck somewhere:
def alarm_signal_handler(signal, frame):
# Something went wrong
exit(1)
However the exit(1) just get caught by the try/except and gets discarded as this is what that specific except does.
This is quite unexpected to me.
In the complete application there will be a lot of try/except and I don't see myself adding
except SystemExit:
exit(1)
or something for all of them.
Any idea how I should handle that use-case?
The dirty way
You can use os._exit instead of sys.exit.
Note that this has obvious drawbacks exactly because it won't go through an exception:
Exit the process with status n, without calling cleanup handlers, flushing stdio buffers, etc.
The proper way
I'd recommend to instead change your exception handling to catch only things inheriting from Exception, because SystemExit doesn't inherit from Exception for precisely this reason, so it won't be caught accidentally:
except Exception:
See also the SystemExit documentation:
This exception is raised by the sys.exit() function. It inherits from BaseException instead of Exception so that it is not accidentally caught by code that catches Exception. This allows the exception to properly propagate up and cause the interpreter to exit.
This also applies to KeyboardInterrupt by the way - Ctrl+C will be caught by except: but not by except Exception:.
It's illustrated pretty well in the exception hierarchy diagram in the Python docs, which you can find here.

Sys.exit(1) after caught exception in python or raise exception again

I wonder how to best handle exceptions in python and to inform the user about it.
I came across the following ideas:
Option 1:
try:
do something
except MyError as e:
logger.error(e)
sys.exit(1)
next code
Option 2:
try:
do something
except MyError as e:
logger.error(e)
raise e
next code
Option: 3
try:
do something
except MyError as e:
logger.error(e)
else:
next code
All three differ in behavior, and you choose which one makes sense in your scenario.
In option #1, you're saying "This is a fatal error, but I want to die 'cleanly' instead of dumping a full traceback to screen."
With option #2, you're saying "I want to log the error, but I'm not handling it; maybe someone higher up the stack will?"; if nobody catches it, it behaves similarly to option #1, aside from (by default) dumping a traceback to the terminal.
With option #3, you're saying "This isn't a fatal error, and we can keep going even if it happens, but certain actions should only be done when the error doesn't occur."
I will note your option #2 is (usually) wrong; you want plain raise to reraise the exception without resetting the traceback or causing exception chaining (which raise e would do), making it behave as if you never caught the exception (aside from the logging output).

How to manage exceptions correctly in PyQt

I am wondering how to manage multiple exceptions in pyqt
I have a function 'encodeVideo()' that may trigger multiple exceptions.
def updateFilename(self):
try:
self.model.updateFilename(self.fileName)
except type_exc.PathIsEmpty as e:
self.errorDialog.errorTypeChanged(e)
self.errorDialog.show()
def updateOutput(self):
try:
self.model.updateOutput(self.encodeDialog.output)
except (type_exc.FileAlreadyExists, type_exc.PathNotExists) as e:
self.errorDialog.errorTypeChanged(e)
self.errorDialog.show()
def encodeVideo(self):
self.updateFilename()
self.updateOutput()
In my case, it is likely to trigger errors both in updateFilname() and updateOutput. When this happens, a dialog will show up and report both errors. However, I seem to manage the exceptions in a wrong way. For example, when error in self.updateFilename() occurs, that doesn't stop my code from continuing the next code self.updateOutput().
You want to handle exceptions as a high as possible in your method call stack; this usually means that exceptions are handled in the UI where the first call was made, if inside any of your methods you need to do something if an exception occurs, you should catch and re-throw the exception, here are some examples:
In your code, the first method that is called from the UI is encodeVideo, therefore, you want to catch and handle your exceptions there:
def updateFilename(self):
self.model.updateFilename(self.fileName)
def updateOutput(self):
self.model.updateOutput(self.encodeDialog.output)
def encodeVideo(self):
try:
self.updateFilename()
self.updateOutput()
except (type_exc.PathIsEmpty, type_exc.FileAlreadyExists, type_exc.PathNotExists) as e:
self.errorDialog.errorTypeChanged(e)
self.errorDialog.show()
Rethrow the exception
Let's imagine that if the call to updatedOutput fails, you want to do something specific, in this case, you can handle the exception in the inner method, but you should rethrow it again so it is handled by the calling method:
def updateOutput(self):
try:
self.model.updateOutput(self.encodeDialog.output)
except type_exc.FileAlreadyExists, e:
print("Do something")
raise type_exc.FileAlreadyExists(e)
def encodeVideo(self):
try:
self.updateFilename()
self.updateOutput()
except (type_exc.PathIsEmpty, type_exc.FileAlreadyExists, type_exc.PathNotExists) as e:
self.errorDialog.errorTypeChanged(e)
self.errorDialog.show()
This is basically a exception and error handling problem. And hence if there is error in any block then system takes it as an error handles or exception handles. Thus if first block of code gave error , then next block contain another handler exception so it is very simple that system treating it as a blocks of errors and exception.

how to fail the step explicitly in behave step implementation

I want to explicitly fail the step in behave when I encounter an exception
eg. I am writing the code according to behave documentation -
from behave import *
#when('verify test fails.*?(?P<param_dict>.*)')
def test_logger(context, param_dict):
try:
logger.info("testing the logger. this is info message")
logger.info(1/0)
except Exception as e:
logger.error("arrived at exception: "+str(e))
fail("failed with exception: "+str(e))
but it throws this error:
NameError: name 'fail' is not defined
I tried other ways too, but nothing works
eg. context.failed = True (did not work either)
If I do not try to fail explicitly, final test result becomes PASS even if it goes in exception block ...which is weird.
context.failed is only an attribute set by Behave and doesn't do anything as is. It's an information attribute, and while you can use it to determine a fail-case and throw an assertion error, it will not do anything on it's own. See context.failed
As for the fail method you've mentioned, it is probably from the unittest module, as seen here. This module is used in Behave's development tests (see their Github) as well. I'll agree though, that this should be clarified in their documentation.
To fix your error you'd need to import the unittest module. To explicitly fail the step, you'd just raise the exception after you've logged your message, something like this:
except Exception as e:
logger.error("arrived at exception: "+str(e))
fail("failed with exception: "+str(e))
raise
As #Verv mentioned in their answer, a behave step will be marked as failed whenever an exception is thrown, so you could just re-raise the exception.
However, behave will show the backtrace for normal exceptions, whereas you probably just want to show a specific failure message.
For that, raise an AssertionError. In your code, that would look like this:
except Exception as e:
logger.error("arrived at exception: " + str(e))
raise AssertionError("failed with exception: " + str(e))
If behave encounters an AssertionError, it will fail the step, display the message you instantiate the error with, but not display other exception stuff.

python ignore whichever line that causes error and continue running the code after that line

I understand the try/except method. What I'm trying to do is:
try:
some_func1() #potentially raises error too
do_something_else() #error was raised
continue_doing_something_else() #continues here after handling error
except:
pass
In the above code, when the error is raised at do_something_else(), the error is handled but then the try statement is exited.
What I want is for python to continue whatever code that is after the line that causes the error. Assuming that an error can happen anywhere in the try statement so I can't just wrap the try/except around do_something_else() itself, is there a way to do this in python?
What you want to do (try with restart) is not possible in Python. Lisp can do it (http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html), and you can implement it in Scheme using call/cc.
Just put the code that you want to do after the possible exception after the except. You may want to use finally (see https://docs.python.org/3/tutorial/errors.html for the documentation):
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception()
finally:
continue_doing_something_else() #continues here after handling error or if no error occurs
if continue_doing_something_else() can also throw exceptions, then put that in a try/except too:
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception1()
try:
continue_doing_something_else()
except:
handle_exception2()
finally:
any_cleanup()
as a general rule, your exception handling should be kept as small in scope as possible while remaining sensible, consider excepting only the 'expected' exceptions and not all of them (e.g. except OSError: when attempting to open files)

Categories