LambdaLabTM
Computer Science · Class 12 · Exception Handling
ExceptionsGood habits⏱️ 14 min read

Catching the Right One

except works with any name from the tree, so it is tempting to write the widest one and stop worrying. Do not. A handler that catches everything is a handler that hides everything — including the bug you had no idea was there.

1The handler that tells you the wrong thing

Here is a program guarding against a division by zero. It is guarding with except Exception, and there is no division by zero in it at all:

too_broad.py
marks = [80, 90, 75]

try:
    print(marks[5])
except Exception:
    print('Cannot divide by zero — please check the denominator.')
Output
Cannot divide by zero — please check the denominator.
Watch Out
That message is a lie, and the program prints it with total confidence. The real fault is marks[5] on a list of three — an IndexError. except Exception caught it because IndexError is under Exception in the tree, and then reported it as something it is not.

Now imagine that inside a two-hundred-line program, and imagine being the person who has to find the bug. The one clue you have points in the wrong direction. The broad handler did not just fail to help — it actively made the debugging harder.

Name the exception you actually expect, and the truth comes out:

specific.py
marks = [80, 90, 75]

try:
    print(marks[5])
except ZeroDivisionError:
    print('Cannot divide by zero — please check the denominator.')
Output
Traceback (most recent call last):
  File "specific.py", line 4, in <module>
    print(marks[5])
          ~~~~~^^^
IndexError: list index out of range

The program crashed — and that is the right outcome. This is a real bug that you did not know about. A traceback naming IndexError and line 4 is far more valuable than a confident sentence about a denominator that does not exist.

Key Takeaway
Catch what you expect. Let what you did not expect crash. An exception you have thought about deserves a handler. An exception you have not thought about deserves a traceback, because that traceback is how you find out it exists.

2How specific? As specific as the tree allows

The last lesson showed that any ancestor will catch an exception. All four of these catch a division by zero — and they get steadily worse from left to right:

ZeroDivisionError
Right

Exactly the thing you expected. Catches nothing else.

ArithmeticError
Wider

Also catches OverflowError. Fine if you meant to, sloppy if you did not.

Exception
Too wide

Catches every mistake in the block — typos, wrong types, missing keys — and reports them all as your one message.

except:
Worst

The bare form. Catches even Ctrl+C and SystemExit, so your program can become genuinely hard to stop.

Watch Out
A bare except: is exactly except BaseException:. Not “similar to” — the same. Leaving the name off does not mean “catch the ordinary ones”; it means catch the whole tree, from the root down, KeyboardInterrupt and SystemExit included.

That is easy to say and easy to forget, so here it is happening. A program asks to quit — sys.exit() is how a program stops itself — and each version tries to catch it:

bare_vs_exception.py
import sys

try:
    sys.exit('the program asked to quit')
except Exception:
    print('except Exception caught it')

print('still running')
Output
the program asked to quit

except Exception did not catch it, so the program quit, and neither print ran. Correct behaviour: a request to stop is not an error in your logic. Now the bare form:

bare_catches_all.py
import sys

try:
    sys.exit('the program asked to quit')
except:
    print('a bare except caught it')

print('still running')
Output
a bare except caught it
still running

The program was told to quit and carried on regardless. Write except BaseException: in place of that bare except: and the output is identical, because they are the same instruction. This is why a bare except: inside a loop can swallow your Ctrl+C and leave you unable to stop the program.

3One try, several except blocks

Being specific does not mean handling only one thing. A try can have as many except blocks as it needs, each naming a different exception, and Python runs the first one that matches:

two_excepts.py
record = {'Riya': 78, 'Amit': 85}

try:
    name = 'Sara'
    print(record[name] / 0)
except KeyError:
    print(name, 'is not in the record.')
except ZeroDivisionError:
    print('Cannot divide by zero.')
Output
Sara is not in the record.

Two things could have gone wrong on that line, and each has its own sentence. The lookup failed first, so the KeyError handler ran — and, as always, the rest of the try block was abandoned, so the division never even happened.

You can also give one handler several exceptions at once, by putting them in brackets, when the response really is the same for all of them:

grouped.py
readings = ['88', 'ninety', '0']

for r in readings:
    try:
        marks = int(r)
        print(r, '->', 100 / marks)
    except (ValueError, ZeroDivisionError):
        print(r, '-> unusable reading, skipped')
Output
88 -> 1.1363636363636365
ninety -> unusable reading, skipped
0 -> unusable reading, skipped

4Put the children before the parents

Python checks the except blocks from top to bottom and takes the first that matches. So if a parent comes first, the child underneath it can never run:

wrong_order.py
try:
    x = 10 / 0
except Exception:
    print('caught by except Exception')
except ZeroDivisionError:
    print('caught by except ZeroDivisionError')
Output
caught by except Exception
except Exception:

Checked first, and ZeroDivisionError is under Exception, so this matches — and Python stops looking.

except ZeroDivisionError:

Unreachable. It is the more specific handler, and it never gets a turn. Python does not warn you about this.

Key Takeaway
Most specific first, most general last. Write the exact exceptions at the top and any wider catch-all at the bottom — the same order you would answer questions in: the precise answer before the vague one.

5The safety net at the bottom

Everything so far has been about naming the exceptions you expect. But you are guessing, and you will guess wrong — there is always some situation nobody pictured. So after all the specific handlers, a real program adds one last except to catch whatever is left.

last_resort.py
marks = [80, 90, 75]

try:
    print(marks[5])
except IndexError:
    print('That position does not exist in the list.')
except Exception as e:
    print('Something went wrong! Please reach out to support@example.com')
    print('Please quote this:', e)
Output
That position does not exist in the list.

Here the specific handler did the work, and the safety net was never needed. Now give it something nobody planned for — a list indexed with a string:

safety_net.py
marks = [80, 90, 75]

try:
    print(marks['first'])          # a mistake nobody planned for
except ZeroDivisionError:
    print('Cannot divide by zero.')
except IndexError:
    print('That position does not exist in the list.')
except Exception as e:
    print('Something went wrong! Please reach out to support@example.com')
    print('Please quote this:', e)
Output
Something went wrong! Please reach out to support@example.com
Please quote this: list indices must be integers or slices, not str

A TypeError, which none of the handlers above it named. The net caught it, the user got a sentence instead of a traceback, and the program ended on its own terms.

What makes it a safety net
  • It is last. Every specific handler is above it, so it only sees what they did not.
  • It claims nothing.“Something went wrong” is honest. It does not name a cause it does not know.
  • It tells the user what to do — an address to write to, so a person who cannot read a traceback still has a next step.
  • It prints e. The real message survives, for the person who gets that email.
What makes it a cover-up
  • It is the only handler. Then it is not a net, it is a blindfold — you never learn what actually happens.
  • It names a cause.“Cannot divide by zero” on a TypeError sends the reader to the wrong line.
  • It throws e away. Then nobody, ever, can find out what went wrong.
  • It is bare. except: also eats Ctrl+C. Use except Exception for a net.
Key Takeaway
The safety net is except Exception, never a bare except:. You want the errors, not the requests to stop. Exception is the root of everything that counts as something going wrong, and it deliberately leaves KeyboardInterrupt and SystemExit alone.

So the finished shape of a well-guarded block reads top to bottom as specific, specific, specific, and then just in case — and every one of those first three is a situation you understood well enough to write a real sentence about.

be_specific.py

6Recap

Name the exception you expect

except ZeroDivisionError, not except Exception. A handler that catches everything hides everything.

A broad handler can lie

It catches the error you did not expect and reports it with the message you wrote for a different one — sending you to the wrong line.

Let the unexpected crash

A traceback naming IndexError and a line number is far more use than a confident sentence about the wrong thing.

except: IS except BaseException:

Leaving the name off does not narrow it. The bare form catches the whole tree, Ctrl+C and SystemExit included, and can make a program hard to stop.

End with a safety net

After every specific handler, one last except Exception as e — a generic message, an address to write to, and print(e) so the real cause survives.

Many excepts, or one with brackets

One handler per exception when the responses differ; except (ValueError, ZeroDivisionError) when they are the same.

Specific first, general last

Python takes the first match. A parent written above its child makes the child unreachable, silently.

✍️ Now write these yourself
  1. 1

    Guard an IndexError with except Exception and a message about division. Notice how convincing the wrong message looks.

    Hint · This is the bug that costs hours in a big program.

  2. 2

    Change it to except ZeroDivisionError and read the traceback you get instead.

    Hint · The crash is more useful than the sentence was.

  3. 3

    Write one try with separate handlers for KeyError and ValueError.

    Hint · Each gets the sentence that actually fits it.

  4. 4

    Put except Exception above except ZeroDivisionError and see which one runs.

    Hint · The first match wins, and Python gives you no warning that the second is unreachable.

  5. 5

    Add a final except Exception as e under your specific handlers, then index a list with a string.

    Hint · A TypeError nobody planned for. The net catches it, and print(e) keeps the real message.

Quick Check

Why is except Exception around marks[5] worse than no handler at all?

Quick Check

What is wrong with putting except Exception above except ZeroDivisionError?

Quick Check

A bare `except:` is the same as which of these?

Quick Check

What belongs in the last except of a well-guarded block?