How to manage exceptions correctly in PyQt - python

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.

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.

Exceptions within exception handling in Python

I'm trying to understand how Python handles exceptions within exception handling. For example, consider the following:
try:
try:
1/0
finally:
raise Exception("Exception!")
except Exception as e:
print(e)
My understanding is that both exceptions thrown by this code (both the ZeroDivisionError and the generic exception thrown in the finally block) should be "handled" by the outside except block...but how does Python decide which one to assign to e? Running the code on my machine, it seems that Python chooses to assign the "most recent" exception (the one thrown in the finally block) to e.
Is this generally true? Also, in a case like this where multiple exceptions might be thrown inside of error handling that are all handled by an outer except block, is there a way for the outer except block to step through each of the errors separately?
The Python docs have this:
If an exception occurs during execution of the try clause, the
exception may be handled by an except clause. If the exception is not
handled by an except clause, the exception is re-raised after the
finally clause has been executed.
So in your example, you don't catch the inner exception, this causes the finally block to execute (before re-raising the original exception). The exception in finally kicks it to the outside block before there is a chance to re-raise the original exception. The outside block never sees the divide-by-zero exception.
This is similar to returning from a function in finally:
def ex_test():
try:
try:
1/0
finally:
return "finally"
except Exception as e:
print(e) # never gets here
ex_test()
# only prints "finally"
# never re-raises the exception
You can get some information about the original exception. From the docs:
When raising (or re-raising) an exception in an except or finally
clause context is automatically set to the last exception caught;
if the new exception is not handled the traceback that is eventually
displayed will include the originating exception(s) and the final
exception.
So:
try:
try:
1/0
finally:
raise Exception("Exception!")
except Exception as e:
print(e.__context__)
# prints: "division by zero"

If I want to catch and log (not raise) exceptions, should KeyboardInterrupt be the only raised exception?

In the case of exceptions, I want the program to catch them, log them, then move on to the next iteration. Obviously a KeyboardInterrupt should still be raised so that the program can be stopped, but are there any other exceptions I should raise?
Very rough example of code below. This is a decorator which catches exceptions and logs them. Basically, should I have any other except cases?
def exception_logger(func):
#wraps(func)
def wrapper(*args, **kwargs):
# Run as normal
try:
return func(*args, **kwargs)
except KeyboardInterrupt:
raise
# Any other exception that occurs is logged
except:
log_file = 'example.txt'
logger = logger_format(log_file)
logger.exception(f'\nAn exception occurred with: {func.__qualname__}\n')
print(f'\n\nAn exception occurred with: {func.__qualname__}\nView the log file for details.\n'.upper())
return wrapper
Thanks.
Instead of a blacklist (that might age poorly), you should just be catching Exception instead of using except:. It excludes KeyboardInterrupt and various others you shouldn’t suppress. (It might be OK to log them, but you don’t seem to want to do that anyway.) See also advice against except: pass in particular for context.

Python exceptions - catching all exceptions but the one expected

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.

How does an exception know if it's being handled?

When raised, SystemExit causes the interpreter to exit, unless the exception is handled.
try:
raise SystemExit()
except SystemExit:
print('not today')
# Continue flow...
I want to mimic that behavior and write my own exception which executes some code only if the exception isn't caught.
Obviously this wouldn't work:
class MyFatalError(Exception):
def __init__(self):
import sys
sys.exit()
try:
raise MyFatalError()
except MyFatalError:
print('never gets here')
(...because an __init__ executes on instantiation, regardless of whether the error is handled.)
Is there a (straightforward and pythonic) way to make an exception behave differently when being handled?
You should not be thinking of SystemExit as a normal exception. All higher-level languages have a function to shut down the process immediately, and that would be sys._exit() in python, but there is also sys.exit() that does kinda the same, but allows your finally blocks to run first, and is implemented as an exception.
What you want should be done by having a try - except block at the lowest level of your application. The behaviour should be in the handler code, not in the exception.

Categories