When does it make sense to use finally
in the try..except
block? Isn't listing statements just after the try..except
doing the same?
What's the difference between these two?
try:
result = 100 / 0
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
finally:
print("final statement")
vs.
try:
result = 100 / 0
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
print("final statement")
Isn't listing statements just after the try..except doing the same?
No, it isn't. You are assuming that you've covered all the ways the block can exit.
finally
is guaranteed to be executed even if the block is exited. That includes return
, continue
or break
, not only exceptions.
For your specific examples, you covered almost every possible path out of the try
block. But you didn't cover KeyboardInterrupt
or MemoryError
however. If someone hits CTRL-C
in the middle of execution, only for the first example would the print("some code at last")
line be executed.
That's because your code doesn't catch KeyboardInterrupt
or MemoryError
, but those exceptions could still occur. If one of the exceptions was raised for the result = 100 / 0
statement, then the exception would not be caught and the whole frame would be exited. But not before the finally:
clause was executed.
It is easier to demonstrate with a different example, one which doesn't catch the exception raised:
mapping = {42: "The answer"}
try:
result = mapping[42] / 17
except KeyError:
print("Oops, no key 42 defined!")
else:
print(result)
finally:
del mapping
Here, the finally
statement will be executed, even though the above raises a TypeError
exception:
>>> mapping = {42: "The answer"}
>>> try:
... result = mapping[42] / 17
... except KeyError:
... print("Oops, no key 42 defined!")
... else:
... print(result)
... finally:
... del mapping
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
>>> mapping # was deleted in the `finally` block
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'mapping' is not defined
And that's what finally
usually is used for, to clean up resources. You can even use it with return
exiting a function:
>>> def foo():
... try:
... return 42
... finally:
... print("Returning doesn't stop finally from executing!")
... print("This is never reached")
...
>>> foo()
Returning doesn't stop finally from executing!
42
Note that Python also has the with
statement and context managers to help do the same kind of work. Context managers encapsulate the clean-up usually done in finally
and let you avoid having to look for the finally
block at the end of a longer piece of code just to check if, say, a file is closed.
So instead of:
fileobj = open(filename)
try:
with line in fileobj:
# many lines of parsing code
# .
# etc.
# .
finally:
fileobj.close()
you can use file objects as a context manager and the above is simplified to:
with open(filename) as fileobj:
with line in fileobj:
# many lines of parsing code
# .
# etc.
# .