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.
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.
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!!
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