As I have promised, here I am with the second posting about handling exceptions. Honestly, neither do I expected for this to happen so soon. 😀
If you have not read the Handling exceptions (1), maybe it would be a good idea for you to do so, before you came back to this one.
Here’s the posting structure:
- local vs. non-local handling
- resource cleanup with finally
- moment of Zen
- platform-specific modules
Local vs. Non-Local Handling
EAFP is standard in Python, and that philosophy is enabled by exceptions. Without exceptions, that is using error codes instead, we are forced to include error handling directly in the main flow of the logic. Since exceptions interrupt the main flow, they allow us to handle exceptional cases non-locally.
- Error codes require interspersed, local handling.
- Exceptions allow centralized, non-local handling.
Exceptions coupled with EAFP are also superior because unlike error codes, exceptions cannot be easily ignored. By default, exceptions have a big effect whereas error codes are silent by default, so the exception EAFP-base style makes it very difficult for problems to be silently ignored.
- Exception require explicit handling.
- Error codes are silent by default.
- EAFP + Exceptions = errors are difficult to ignore!
Resource Cleanup with Finally
- …lets us clean up whether an exception occurs or not.
An understanding of this block is useful for making our own context managers. Consider the following function which uses various facilities of the Standard Library’s OS module to change the current working directory, create a new directory at that location, and then restore to the original working directory.
At the first sight, this seems reasonable, but if the call of os.mkdir() fails for some reasons, the current working directory of the Python process won’t be restored to its original value, and the make_at() function will have had an unintended side effect.
To fix this, we would like the function to restore the original current working directory under all circumstances. We can achieve this with a try…finally block.
Code in the finally-block is executed whether execution lives the try-block normally, by reaching the end of the block, or exceptionally by an exception being raised.
Finally-blocks is executed no matter how the try-block exists!
This construct can be combined with except blocks, here used to add a simple further logging facility:
In the code above, if the os.mkdir() call rises an OSError, the OS handler will be run, and the exception will be re-raised. Since the finally block is always run no matter how the try block ends, we can be sure that the final directory change will take place in all circumstances.
Moment of Zen
Errors should never pass silently, unless explicitly silenced. Errors are like bells and if we make them silent, they are not of no use.
Detecting a single keypress from Python, such as the press any key to continue functionality at the console, requires use of operating system specific modules. We cannot use the built-in input function because that waits for the user to press Return before giving us a string. To implement this on Windows, we need to use functionality from the Windows only msvcrt module, and on Linux and Mac OS X we need to use functionality from the Unix only tty and termios modules in addition to the sys module.
The above example is quite instructive, as it demonstrates many Python language features, including import and def, as statements as opposed to declarations.
Recall the top level module code is executed on first import. Within the first try block we attempt to import msvcrt, the Microsoft Visual C runtime. If this succeeds, we then proceed to define a function getkey(), which delegates to the msvcrt.getch() function. Even though we are inside a try block at this point, the function will be declared at the current scope, which is the module scope. If, however, the import of msvcrt fails because we’re not running on Windows, an ImportError will be raised, and execution will transfer to the except block.
This is a case of an error being silenced explicitly because we’re going to attempt an alternative course of action in the exception handler. Within the except block we import three modules needed for a getkey() implementation on Unix-like systems, and then proceed to the alternative definition of getkey(), which again binds the function implementation to a name in the module scope.
This Unix implementation of getkey() uses a try…finally construct to restore various terminal attributes after the terminal has been put into raw mode for the purposes of reading a single character.
In the event that our program is running on neither Windows nor a Unix-like system, the import tty statement will raise a second import error. This time we make no attempt to intercept this exception. We allow it to propagate to our caller, which is whatever attempted to import this keypress module. We know how to signal this error, but not how to handle it, so we defer that decision to our caller. The error will not pass silently.
If the caller has more knowledge or alternative tactics available, it can in turn intercept this exception and take appropriate action, perhaps degrading to using Python’s input built-in function and giving a different message to the user.
- The raising of an exception interrupts normal program flow and transfers control to an exception handler.
- Exception handlers are defined using the try…except
- try blocks define a context for detecting exceptions.
- Corresponding except blocks handle specific exception types.
- Python uses exceptions pervasively.
- Many built-in language features depend on them.
- except blocks can capture an exception object, which is often of a standard type.
- Programmer errors should not normally be handled.
- Exceptional conditions can be signaled using raise.
- raise without an argument re-raises the current exception.
- Generally do not check for TypeErrors.
- Exception objects can be converted to strings using the str().
- A function’s exceptions form part of its API.
- They should be documented properly.
- Prefer to use built-in exception type when possible.
- Use the try…finally construct to perform cleanup actions.
- May be used in conjunction with except
- Output of the print() can be redirected using the optional file
- Use and and or for combining boolean expressions.
- Return codes are too easily ignored.
- Platform-specific actions can be implemented using EAFP along with catching ImportErrors.