Python finally Block

In the last lesson, we learned about Python Multiple Exceptions. Now let’s use finally for code that must always run.

Let’s Talk About Cleanup

We know about try and except. We put risky code in try. We handle errors in except.

But sometimes, there is something we need to do no matter what. Whether an error happens or not. Whether the code succeeds or fails.

Maybe we opened a file. We need to close it. Maybe we started a database connection. We need to close it. Maybe we locked something. We need to unlock it.

That is what the finally block is for.

What Is finally in Python?

finally is a block of code that always runs. No matter what happens in the try block.

  • If no exception happens, finally runs.
  • If an exception happens and is handled, finally runs.
  • If an exception happens and is not handled, finally runs before the program crashes.

It always runs. That is the whole point.

The Structure of Python try-except-finally

try:
# code that might raise an exception
except:
# handle the exception
finally:
# this always runs

The finally block is optional. But when we use it, it goes after all the except blocks.

We can also have try and finally without except.

try:
# some code
finally:
# cleanup code

In this case, if an exception happens, it is not caught. It will still crash the program. But the finally block runs first. Then the exception propagates.

That is useful when we just want to make sure cleanup happens, even if we cannot handle the error.

A Simple finally Example in Python

Let’s see finally in action.

try:
print("Trying something")
print(10 / 0)
except ZeroDivisionError:
print("Caught division by zero")
finally:
print("This always runs")

Output

Trying something
Caught division by zero
This always runs

The finally block ran. Even though there was an exception.

Now let’s see without an exception.

try:
print("Trying something")
print(10 / 2)
except ZeroDivisionError:
print("Caught division by zero")
finally:
print("This always runs")

Output

Trying something
5.0
This always runs

No exception. But finally still ran.

That is the key idea. finally always runs.

finally with Unhandled Exception in Python

What if the exception is not handled? Let’s see.

try:
print("Trying something")
print(10 / 0)
finally:
print("This always runs")

Output

Trying something
This always runs
Traceback (most recent call last):
File "example.py", line 3, in <module>
print(10 / 0)
ZeroDivisionError: division by zero

The finally block ran before the program crashed. So even if we cannot handle the error, we can still clean up.

Why Is finally Useful in Python?

The most common use is closing resources.

When we open a file, we should close it when we are done. Even if an error happens while reading or writing.

file = open("data.txt", "r")
try:
content = file.read()
print(content)
finally:
file.close()

If an error happens during file.read(), the finally block still closes the file. That prevents file handles from leaking.

We can also use finally for other cleanup tasks. Like closing a network connection. Or releasing a lock. Or deleting temporary files.

finally with return in Python

Here is something interesting. If we have a return statement in the try or except block, the finally block still runs before the function returns.

def test():
try:
return "From try"
finally:
print("Finally runs")
print(test())

Output

Finally runs
From try

See? The finally block ran before the return value was sent back. So even if we return early, cleanup still happens.

This is a very important behavior. It means we cannot skip the finally block.

finally with break or continue in Python

Similarly, if we have a loop with break or continue inside a try block, the finally block still runs.

for i in range(5):
try:
if i == 3:
break
print(i)
finally:
print("Finally for", i)

Output

0
Finally for 0
1
Finally for 1
2
Finally for 2
Finally for 3

When i is 3, break is executed. But before the loop exits, the finally block runs for that iteration.

That is the guarantee. finally always runs.

Python finally Block: A Real-Life Example

Let’s say we are writing to a file. We want to make sure the file is closed, even if something goes wrong.

try:
file = open("output.txt", "w")
try:
file.write("Hello, world!\n")
file.write("This is a test.\n")
x = 10 / 0
finally:
file.close()
print("File closed")
except ZeroDivisionError:
print("Caught division by zero")

Output

File closed
Caught division by zero

The inner finally closed the file. Then the outer except caught the division by zero.

That is a clean way to ensure cleanup.

A Common Pattern: try-finally for Cleanup

Sometimes we do not want to handle the exception. We just want to clean up and let the exception propagate.

def read_file(filename):
file = open(filename, "r")
try:
return file.read()
finally:
file.close()

If the file does not exist, open() raises FileNotFoundError. The finally block does not run because the file was never opened. But if the file opens successfully, the finally block runs and closes it.

If an error happens during read(), the finally block closes the file, and the exception propagates.

That is a robust pattern.

Common Mistakes with Python finally Block

Mistake 1: Putting Cleanup Code in the Wrong Place

try:
file = open("data.txt", "r")
content = file.read()
file.close() # This might not run if an error happens before it
except:
print("Error")

If file.read() raises an exception, file.close() is never called. The file stays open. That is a leak.

So what do we do? We put cleanup in finally.

try:
file = open("data.txt", "r")
content = file.read()
except:
print("Error")
finally:
file.close()

But let’s be careful: if open() fails, file is not defined. So we need to handle that.

file = None
try:
file = open("data.txt", "r")
content = file.read()
except:
print("Error")
finally:
if file:
file.close()

Or better, we use a with statement. But that is a topic for later.

Mistake 2: Using finally to Return a Value

def test():
try:
return "From try"
finally:
return "From finally"

Output

From finally

The finally block runs last. So its return value overrides the earlier one. That is confusing. Let’s avoid returning from finally.

Mistake 3: Putting Too Much Code in finally

Let’s keep the finally block simple. It should just do cleanup. Let’s not put logic that can fail. Because if finally raises an exception, it will mask the original exception.

Mistake 4: Forgetting That finally Runs Even on Unhandled Exceptions

If we have a try with finally but no except, and an exception happens, the finally runs, then the exception propagates. That is correct behavior. But some people forget it.

Next, we will learn about the Python raise Statement and create an exception when our code detects a problem.