TIL - Python 3.14 and 'finally' blocks - PEP 765
In Python versions prior to 3.14 the finally
block is used as part of exception handling to guarantee that certain cleanup code runs, regardless of whether an exception was raised or not in the try
block.
This is especially useful for releasing resources such as files or network connections.
Basic behaviour
- The code inside
finally
will always execute, whether or not an exception occurs in the try or except blocks. - We don’t have to use except for
finally
to work. Atry
/finally
combination is valid and common.
One example is closing a file no matter what (even if there’s an error while reading it)
try:
file = open("example.txt", "r")
# Some code that may raise an exception
finally:
file.close() # Always runs, even if there was an error above
One quirky thing in older Python versions, is that we can use return
, break
, or continue
statements inside finally
. These statements would override return
values and exceptions from the try or except block, which can lead to confusing or buggy code. This is not intuitive.
Here’s an example:
def func():
try:
return "try"
finally:
return "finally" # This overrides the previous return
print(func()) # Outputs: "finally"
The problem is that if an exception is raised in try
, a return
or break
in finally
will suppress that exception.
Here’s an example:
def func():
try:
raise Exception("error")
finally:
return "suppressed" # Exception is swallowed!
print(func()) # Outputs: "suppressed"
What changed?
In Python 3.14, there is a significant change regarding the use of control flow statements (specifically return
, break
, and continue
) in finally
blocks. As per PEP 765, Python 3.14 now disallows these statements if they would cause the flow to exit from a finally
block. This means that attempting to use return
, break
, or continue
to break out of a finally
block will raise a SyntaxError in Python 3.14.
Here’s an example of code that would work in older Python but raise a Syntax Error in Python 3.14:
def test():
try:
raise Exception("Oops")
finally:
return "Finally Return" # In Python 3.14, this now causes SyntaxError
What is still allowed?
Using return
, break
, or continue
inside control structures (like a loop) that are themselves inside a finally
block is allowed, as long as they don’t exit the finally
block itself. For example, breaking out of a loop within a finally
block (but not out of the finally block) is permitted.
Here’s an example:
def demo():
try:
print("In try")
finally:
for i in range(3):
print("Loop", i)
if i == 1:
break # This is allowed: breaks out of loop, not the finally block
print("After loop in finally") # This will run after the loop
demo()
Conclusion
It’s more intuitive. But if we have code that uses return
, break
, or continue
statements in finally
blocks that exit the block, then we should refactor it for Python 3.14 compatibility.