I've been working on code that needs to handle unexpected system shutdowns gracefully. It's critical for me to avoid leaving the system in an unknown state, especially since I'm working on a server where I can't control reboots. I've typically been trapping SIGINT and setting a flag for my code to check when this happens. This got me thinking about where exactly a KeyboardInterrupt can be thrown in my code.
For instance, when working with a resource that doesn't use a context manager (like netCDF4's Dataset), I often write code like this:
```python
handle = None
try:
handle = netCDF4.Dataset(filepath, "r")
# do stuff
finally:
if handle is not None:
handle.close()
```
I do it this way because I'm worried that if I create the Dataset before the try block and a KeyboardInterrupt occurs between that line and the try statement, the resource won't get closed properly. Is it possible for a KeyboardInterrupt to happen between completing the statement that assigns to handle and actually entering the try block? And if that's the case, is it the Dataset class's responsibility to handle the closing of the file during construction? I apologize for the complexity—it's just something that's been on my mind with the coding I've been doing lately.
3 Answers
It's likely that KeyboardInterrupt can occur almost anywhere in your code. If it happens between assigning `handle = None` and entering the try block, there'd be nothing to clean up then. However, if you don't catch KeyboardInterrupt, Python will still release the file handle during its own cleanup process. Worst case, if the process ends unexpectedly, the OS should close it.
Why not just use an except block in between your try and finally blocks? Or, if you're feeling adventurous, skip duck typing for the exception handling altogether!
KeyboardInterrupt can indeed occur at various points in your code. However, since netCDF4 is built with Cython, the handling of interrupts during its execution might not be straightforward. To simplify, you can leverage the fact that the Dataset class itself has a context manager. You could write it like this:
```python
with netCDF4.Dataset(filepath, "r") as handle:
# do stuff
```
In this setup, the `__exit__` method will always ensure proper cleanup, just like a try/finally block would. Although assigning `handle` before the try statement does introduce some risk, it’s less concerning than it seems. Allocating resources within a `with nogil` block means the C/C++ code runs without interruption, making an interrupt occurring in that narrow window rather improbable.
If you need to access `handle` outside the block, then managing everything in the context block is likely the safest route.
Oh, I didn’t realize it had a context manager! That definitely sounds safer.

True! My focus is more on my own cleanup, though, rather than just the file handle release.