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:
marks = [80, 90, 75]
try:
print(marks[5])
except Exception:
print('Cannot divide by zero — please check the denominator.')Cannot divide by zero — please check the denominator.
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:
marks = [80, 90, 75]
try:
print(marks[5])
except ZeroDivisionError:
print('Cannot divide by zero — please check the denominator.')Traceback (most recent call last):
File "specific.py", line 4, in <module>
print(marks[5])
~~~~~^^^
IndexError: list index out of rangeThe 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.
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:
ZeroDivisionErrorExactly the thing you expected. Catches nothing else.
ArithmeticErrorAlso catches OverflowError. Fine if you meant to, sloppy if you did not.
ExceptionCatches every mistake in the block — typos, wrong types, missing keys — and reports them all as your one message.
except:The bare form. Catches even Ctrl+C and SystemExit, so your program can become genuinely hard to stop.
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:
import sys
try:
sys.exit('the program asked to quit')
except Exception:
print('except Exception caught it')
print('still running')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:
import sys
try:
sys.exit('the program asked to quit')
except:
print('a bare except caught it')
print('still running')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:
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.')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:
readings = ['88', 'ninety', '0']
for r in readings:
try:
marks = int(r)
print(r, '->', 100 / marks)
except (ValueError, ZeroDivisionError):
print(r, '-> unusable reading, skipped')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:
try:
x = 10 / 0
except Exception:
print('caught by except Exception')
except ZeroDivisionError:
print('caught by except ZeroDivisionError')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.
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.
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)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:
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)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.
- 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.
- 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
TypeErrorsends the reader to the wrong line. - It throws
eaway. Then nobody, ever, can find out what went wrong. - It is bare.
except:also eats Ctrl+C. Useexcept Exceptionfor a net.
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.
6Recap
except ZeroDivisionError, not except Exception. A handler that catches everything hides everything.
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.
A traceback naming IndexError and a line number is far more use than a confident sentence about the wrong thing.
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.
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.
One handler per exception when the responses differ; except (ValueError, ZeroDivisionError) when they are the same.
Python takes the first match. A parent written above its child makes the child unreachable, silently.
- 1
Guard an
IndexErrorwithexcept Exceptionand a message about division. Notice how convincing the wrong message looks.Hint · This is the bug that costs hours in a big program.
- 2
Change it to
except ZeroDivisionErrorand read the traceback you get instead.Hint · The crash is more useful than the sentence was.
- 3
Write one
trywith separate handlers forKeyErrorandValueError.Hint · Each gets the sentence that actually fits it.
- 4
Put
except Exceptionaboveexcept ZeroDivisionErrorand see which one runs.Hint · The first match wins, and Python gives you no warning that the second is unreachable.
- 5
Add a final
except Exception as eunder your specific handlers, then index a list with a string.Hint · A
TypeErrornobody planned for. The net catches it, andprint(e)keeps the real message.
Why is except Exception around marks[5] worse than no handler at all?
What is wrong with putting except Exception above except ZeroDivisionError?
A bare `except:` is the same as which of these?
What belongs in the last except of a well-guarded block?