runtime
88 / 250
lesson 88 Exceptions & Validation · intermediate

Success-only and always: else and finally

`else` holds code that should run only when the `try` succeeded. `finally` holds code that must run no matter what, even when an error is on its way out.

You'll be able to
  • Trace the order in which try, except, else and finally blocks run
  • Move success-only work into else, so try covers only the risky line
  • Use finally for bookkeeping that must happen on every path, including an unhandled error
The idea

A try statement can have two more blocks. `else` runs only if the try block finished without an error: put the code that depends on success there, so it isn't accidentally protected by the except. `finally` runs last, on every path: success, a handled error, or an error that nobody catches. It exists for clean-up and bookkeeping that must not be skipped.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Which blocks run
def attempt(text):
    try:
        amount = float(text)
    except ValueError:
        print(" except: not a number")
    else:
        print(" else: charged", amount)
    finally:
        print(" finally: attempt logged")

print("A")
attempt("19.99")
print("B")
attempt("free")

finally even runs when the error isn't handled at all. Its block runs first, then the error carries on and stops the program as before.

example-2.py · finally runs, then the error continues
status = {"running": True}
try:
    print(int(None))
except ValueError:
    print("bad text")
finally:
    status["running"] = False
    print("status:", status)

In real code finally releases whatever the work was holding: a "busy" flag, a counter of attempts, and in the Files & Directories module an open file. Here a payment processor counts every attempt, even ones that crash, so the dashboard's attempt total is never short.

example-3.py · Every attempt is counted
def charge(stats, text):
    stats["attempts"] += 1
    try:
        amount = float(text)
    except ValueError:
        stats["rejected"] += 1
        return None
    else:
        stats["charged"] += amount
        return amount
    finally:
        print(f"after {text!r}: {stats}")

stats = {"attempts": 0, "rejected": 0, "charged": 0}
charge(stats, "12.93")
charge(stats, "twelve")

When to use it: else when work should happen only after the risky step succeeded; finally for anything that must happen whatever the outcome. Mistakes to avoid: putting success-only work inside try, where an unrelated error in it would be caught by the wrong handler; and returning a value from finally, which silently replaces the function's real result and swallows errors.

Review
  • try for the risky line, except for the failure, else for success-only work, finally for always.
  • finally runs even when the error isn't handled, and even after a return.
  • Keep success-only work out of try, or the wrong handler may catch its bugs.
  • Next: raising your own errors, with messages that say exactly what was wrong.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Success-only and always: else and finally — Exceptions & Validation · Runtime