try, except, else and finally

try runs risky code, except handles a failure, else runs when nothing failed, and finally always runs. Each block has one job and using the wrong one hides bugs.

The basic form

try:
    value = int(input("Enter a number: "))
    print("You entered", value)
except ValueError:
    print("That was not a whole number.")

Python attempts the try block. If an exception of the named type occurs, the except block runs and the program continues. If no exception occurs, the except block is skipped entirely.

Catching the right thing

data = {"a": 1}

# Too broad - hides every mistake, including typos
try:
    print(data["b"])
except Exception:
    print("something went wrong")

# Right - names exactly what can fail and why
try:
    print(data["b"])
except KeyError:
    print("that key is not present")
Catch the narrowest exception that describes the failure you expect. A broad except Exception will happily swallow a misspelled variable name and report it as "something went wrong", turning a two second fix into an afternoon.

Never write a bare except

# Do not do this
try:
    risky()
except:                # catches EVERYTHING, including KeyboardInterrupt
    pass

A bare except: catches KeyboardInterrupt and SystemExit as well, so the program cannot be stopped with Ctrl+C and cannot exit cleanly. If you genuinely must catch broadly, write except Exception:, which excludes those two.

Catching several exceptions

def divide(text_a, text_b):
    try:
        return int(text_a) / int(text_b)
    except ValueError:
        return "both values must be whole numbers"
    except ZeroDivisionError:
        return "cannot divide by zero"


print(divide("10", "2"))       # 5.0
print(divide("10", "x"))       # both values must be whole numbers
print(divide("10", "0"))       # cannot divide by zero
# One handler for several types
try:
    risky()
except (ValueError, TypeError, KeyError):
    print("bad input of some kind")

Handlers are checked in order and only the first match runs. Order them from the most specific to the most general, or the general one will shadow the rest.

Inspecting the exception object

try:
    int("abc")
except ValueError as error:
    print(type(error).__name__)     # ValueError
    print(error)                    # invalid literal for int() with base 10: 'abc'
    print(error.args)               # a tuple of the arguments it was raised with
try:
    open("missing.txt")
except FileNotFoundError as error:
    print(error.filename)           # missing.txt
    print(error.errno)              # 2
    print(error.strerror)           # No such file or directory

else

try:
    value = int("42")
except ValueError:
    print("conversion failed")
else:
    print("conversion succeeded:", value)     # only when nothing was raised
finally:
    print("always runs")

Why not put the success code inside try? Because the try block should contain only the line that might fail. Anything else in there is protected by the handler as well, which is exactly the accidental over-catching to avoid:

records = {"count": "abc"}

# Wrong: the handler now also catches failures from process()
try:
    count = int(records["count"])
    process(count)                     # if THIS raises ValueError, it is misreported
except ValueError:
    print("bad count")

# Right: try holds only the risky conversion
try:
    count = int(records["count"])
except ValueError:
    print("bad count")
else:
    process(count)

finally

def read_first_line(path):
    handle = None
    try:
        handle = open(path)
        return handle.readline()
    except FileNotFoundError:
        return None
    finally:
        if handle:
            handle.close()
            print("file closed")

finally runs no matter what: after a successful try, after a handled exception, after an unhandled one on its way up, and even after a return. It exists for cleanup - closing files, releasing locks, restoring state.

def demo():
    try:
        return "from try"
    finally:
        print("finally still runs")


print(demo())
finally still runs
from try

A return in finally swallows exceptions

def bad():
    try:
        raise ValueError("important")
    finally:
        return "hides the error"       # the exception disappears


print(bad())      # hides the error - the ValueError is gone

Never return, break or continue from a finally block. It discards any exception in flight, and the failure vanishes without trace.

with is usually better than finally

# Manual cleanup
handle = open("data.txt")
try:
    content = handle.read()
finally:
    handle.close()

# The same guarantee, with the cleanup built in
with open("data.txt") as handle:
    content = handle.read()

A context manager closes the file however the block exits. Use with for anything that must be released; keep finally for cleanup that has no context manager of its own.

The full order of execution

def demo(value):
    print("--- ", value)
    try:
        print("try")
        result = 10 / value
    except ZeroDivisionError:
        print("except")
        return "handled"
    else:
        print("else")
        return result
    finally:
        print("finally")


print(demo(2))
print(demo(0))
---  2
try
else
finally
5.0
---  0
try
except
finally
handled

Ask forgiveness, or ask permission

config = {"retries": 3}

# Look before you leap
if "timeout" in config:
    timeout = config["timeout"]
else:
    timeout = 30

# Easier to ask forgiveness
try:
    timeout = config["timeout"]
except KeyError:
    timeout = 30

# Best here: the method exists for exactly this
timeout = config.get("timeout", 30)

Python leans towards trying the operation and handling the failure, because a check followed by an action can go stale between the two. Use a check when failure is likely and cheap to test for; use try when failure is genuinely exceptional.

Nesting and re-raising

def load(path):
    try:
        with open(path) as handle:
            return handle.read()
    except FileNotFoundError:
        print(f"warning: {path} is missing, using defaults")
        return ""
    except PermissionError:
        print(f"cannot read {path}")
        raise                     # log it, then let the caller deal with it

A bare raise inside a handler re-raises the current exception with its original traceback intact. Use it when you want to observe a failure without taking responsibility for it.

Common mistakes

  • Writing a bare except:.
  • Catching Exception when a specific type was known.
  • Putting more than the risky line inside try.
  • Using except ... : pass with no comment, silently discarding failures.
  • Returning from finally and losing the exception.
  • Using exceptions for ordinary control flow where an if would be clearer.

Best practices

  • Keep try blocks as short as possible and move success code into else.
  • Catch specific exception types and name them with as when the details matter.
  • Use with for resources; use finally only when there is no context manager.
  • Log or report before swallowing an error, and never swallow one silently.
  • Re-raise with a bare raise when the caller should still know.

Practice

  1. Write an input loop that keeps asking until a valid positive integer is entered.
  2. Rewrite a function that uses try around five lines so that only the risky line is inside it.
  3. Demonstrate that finally runs after a return, and then that a return in finally hides an exception.
  4. Write a file reader that reports a missing file, re-raises a permission error, and always reports that it finished.
  5. Show the same lookup handled three ways: with in, with try, and with get. Say which you prefer and why.

Conclusion

Put only the risky line in try, name the exception you expect, put the success path in else and the cleanup in finally - or better, in a with block. Never write a bare except, and never return from finally.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →
Python

The Exception Hierarchy

Every exception is a class in one inheritance tree. Catching a parent catches all its children, which is what makes handler order and handler width ma...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.