Skip to content
Theory · Exceptions · lesson 4 of 11

else and finally

Picture yourself mixing dough for a pie. If the dough turns out well, you put it in the oven. If it does not, you throw it away. And you wash the bowl in any case, whatever happens.

try has branches for exactly these three cases:

  • the except branch is "it did not work";
  • the else branch is "it worked, let's go on";
  • the finally branch is "wash the bowl in any case".

The else and finally branches go after all the except branches, at the same level as try:

Python
def show_double(text):
    try:
        n = int(text)
    except ValueError:
        print("bad", end=" ")
    else:
        print(n * 2, end=" ")
    finally:
        print("checked")


show_double("5")  # 10 checked
show_double("x")  # bad checked

Let's see what happens on each call.

The call show_double("5"):

  1. int("5") goes through, and n becomes 5.
  2. The except branch is skipped, and else prints 10.
  3. The finally branch prints checked on the same line.

The call show_double("x"):

  1. int("x") raises a ValueError, and the try block breaks off.
  2. The except branch prints bad, and else is skipped.
  3. The finally branch prints checked again.

The else branch

The else branch runs only if no exception happened in the try block. This word has already appeared with loops, but here it means something else: else on a try means "there was no error".

The same print(n * 2) could have stayed inside the try. But then except ValueError would also catch errors from that line, while only int(text) needs guarding. The else holds code whose errors this try must not catch.

Remember the rule: the try holds only the line that may fail in the expected way. Everything that goes on after its success moves to else.

The finally branch

The finally branch always runs. When exactly:

  • after a successful try;
  • after an except that ran;
  • even when nobody caught the exception—then Python runs finally first and prints the traceback after it.

That is why finally holds closing actions that must not be skipped. Here the bowl picture ends: in a kitchen you can forget to wash it, but Python never forgets the finally branch.

A try with a finally branch may have no except at all. If a function returns from try, except or else, finally still runs—before the value goes back to the caller:

Python
def half(text):
    try:
        return int(text) // 2
    finally:
        print("half of", text, end=": ")


print(half("9"))  # half of 9: 4

Let's go step by step:

  1. The call half("9") computes 4 on the line with return.
  2. Before the function exits, finally runs and prints half of 9: .
  3. Only after that does the outer print get 4 and print it on the same line.

The call half("x") would also print half of x: , but then the program would stop with a traceback: nobody caught the ValueError.

Interviewers like to ask: "Does finally run if the try has a return?" The answer is yes, it runs, and before the value goes back to the caller.

Common mistake

A return is written in finally:

Python
def to_int(text):
    try:
        return int(text)
    finally:
        return 0  # ✗


print(to_int("7"))  # 0
print(to_int("x"))  # 0

Python 3.12 reports no error. The return in finally replaces the value from try, so to_int("7") returns 0. It cancels the exception too: the ValueError from int("x") disappears with no traceback. Keep only closing actions in finally, and return the value from try, except or else.

Key points

  • The else branch runs only if the try raised no exception; it holds code whose errors the try must not catch.
  • The finally branch always runs: after success, after except, and before the traceback of an uncaught exception.
  • On a return from try, the finally branch runs before the value goes back to the caller.
  • A return in finally replaces the value and cancels the exception, so finally holds only closing actions.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way