Try except bubbling?

Trying to understand how errors are shown (i’m not doing anything practical with this now so can’t come up with a useful example off the cuff, just understanding the language feature):

try:
    with open('doesnotexist') as f:
        f.read()
except:
    print('EXCEPT!')
    #f.close()

i see the except with no exception type specified will handle all exceptions, FileNotFoundError in this case, so the output only prints EXCEPT!.
However, if i uncomment f.close, 2 errors come out too.

EXCEPT!
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-20-4da32cc4fcd6> in <module>
      1 try:
----> 2     with open('doesnotexist') as f:
      3         f.read()

FileNotFoundError: [Errno 2] No such file or directory: 'doesnotexist'

During handling of the above exception, another exception occurred:

NameError                                 Traceback (most recent call last)
<ipython-input-20-4da32cc4fcd6> in <module>
      4 except:
      5     print('EXCEPT!')
----> 6     f.close()

NameError: name 'f' is not defined

I wasn’t expecting the FileNotFoundError to appear. I thought the except block will handle it (not sure what the word “handle” actually does/implies to code downstream), so the FileNotFoundError will not be shown and only the NameError should appear?
Has this got something to do with exception bubbling? (I understand this to mean exceptions not being printed at the scope of generation, but passed up through layers of functions to be printed later in a higher calling scope)

Another related question:

try:
    with open('doesnotexist') as f:
        f.read()
finally:
    print('FINALLY!')
    f.close()

Output:

FINALLY!
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-8-73c6b36713d0> in <module>
      1 try:
----> 2     with open('doesnotexist') as f:
      3         f.read()

FileNotFoundError: [Errno 2] No such file or directory: 'doesnotexist'

During handling of the above exception, another exception occurred:

NameError                                 Traceback (most recent call last)
<ipython-input-8-73c6b36713d0> in <module>
      4 finally:
      5     print('FINALLY!')
----> 6     f.close()

NameError: name 'f' is not defined

What does open return when using default ‘r’ with FileNotFoundError?
When does control pass from try block to finally? Is it immediately when the open() gives an error? Because my first reaction was why NameError is pointing to ----> 6 f.close() but not the f or f.read() in the try block

The NameError occurs during handling of the FileNotFoundError, effectively meaning that handling of the latter failed. In the traceback, the NameError is therefore traced back to the handling of the original error - that’s why both are shown.

Does this make sense?

Related to your second question, it doesn’t quite make sense to use a context manager within a try...finally block as these control structures overlap in functionality.

From the book Fluent Python:

The with statement was designed to simplify the try/finally pattern which guarantees that some operation is performed after a block of code, even if the block is aborted because of an exception, a return or sys.exit() call. The code in the finally clause usually releases a critical resource or restores some previous state that was temporarily changed.

The context manager protocol consists of the enter and exit methods. At the start of the with, enter is invoked on the context manager object. The role of the finally clause is played by a call to exit on the context manager object at the end of the with block.

The most common example is making sure a file object is closed.

Does that traceback reporting go back more than 1 level? (such that i can see multiple During handling of the above exception, another exception occurred: ).

Thanks for sharing this idea of simplifying try/finally. Never occurred to me with is designed for that. I got that block of code from triplebyte data science quiz, they were asking if the file is closed or not when the file is not found.

When Python encounters an error during runtime, it retraces it as far as possible. In your example, the file could not be closed because it wasn’t opened to begin with, and the error was traced back to the point in the try block where Python tried to open the file.

Not sure if this answers your question.

If you are trying to gain general insight into how Python executes code, here are two resources that I’ve found very helpful:

  • Python Tutor lets you visualize the execution of code
  • Python’s built-in dis module let’s you take a look at the bytecode instructions behind your code. It’s quite headache inducing though - just a warning!!
try:
    with open('doesnotexist') as f:
        f.read()
except:
    print('EXCEPT!')
    try:
        raise ValueError
    except:
        print('ValueError caught')
        f.close()

Trying to simulate this (above is successful) opened up the nested try-except concept to me.
Anyway pythontutor breaks with

Exception: open() is not supported by Python Tutor.
Instead use io.StringIO() to simulate a file.
Example: http://goo.gl/uNvBGl

when it reaches open

Selecting “Python with Anaconda 3.6” instead of plain Python in the edit window could work around this, I think.

Yep now it steps through. But can’t seem to demonstrate the Error printing as a batch.

I’m not sure I understand what you are trying to do.

When an error is raised in the try block, the execution moves on to the except block.

try:
    print(0/0)
except ZeroDivisionError:
    pass

Now if you try to execute this, nothing happens. The ZeroDivisionError happens but you are not made aware of it. But it’s still there.

try:
    print(0/0)
except ZeroDivisionError:
    print(message)

As message is not defined, we get a NameError. And we see the ZeroDivisionError in the Traceback. Why?

Well, the ZeroDivisionError was never actually handled - the execution of the except block that was supposed to take care of that failed.

When the execution moves from the try to the except block, the try block is not eliminated from the call stack until the except block has been successfully executed. So when an error is raised in the except block, it is retraced to the try block, which is the control structure that called it in the first place.

I was trying to see if pythontutor can visualize to me that the first FileNotFoundError is saved somewhere to be shown later if exception block does not complete. Seems like it doesn’t, but that’s fine, a conceptual understanding of this behaviour is enough. Was there any tool that shows the call stack that makes you say try block is not eliminated? I can’t see a call stack on pythontutor

No, that’s just the way error traceback works. If you find a way to visualize it, let me know :slight_smile: