Python exceptions for C programmers
If you are a C programmer, the simple way to think about Python exceptions is that they are almost always error returns; where a C function would be documented as 'returns whatever, or MY_ERROR on error', an idiomatic Python equivalent is 'returns whatever, or raises MyException on error'. Whether this works better (and when) is a matter of some debate, but it is the Pythonic approach and is used by most modules that you'll run into.
(Ideally the modules will document this. Note that it's relatively common to just describe the module's exceptions and generally what when they get raised, and then not annotate every function and method with what exceptions it raises.)
Thus, you deal with exceptions in more or less the same way that you'd deal with error returns, with the exception that you don't have the option of ignoring them; either you handle them or your program dies. The immediate corollary is that if a program ever dies with a Python stack backtrace, the fault is usually in some code that didn't handle an error return.
(Of course the program might have run into an impossible situation or an internal bug, but this is generally uncommon. One hopes.)
Doing something useful with exceptions that you catch is another issue, unfortunately; many error return exceptions contain very little immediately useful information about what exactly went wrong (some contain information that can be painstakingly decoded if you know what the module that generated them is doing). It's still worth explicitly catching them in programs, if only to tell the user 'something went wrong with <X>' in a somewhat friendly manner.
(This situation is not really much different from getting generic
'something went wrong' error returns from libraries in C. By the way, I
strongly suggest avoiding broad
try's because they will
mask genuine coding bugs that you want to hear about.)
Exceptions don't have to be handled exactly like C error returns,
where you wrap each separate call in your function with its own
except: block; I can think of at least two additional patterns.
First, if all you are going to do with the error is to stop doing
anything in the function and return an error indicator to your caller,
you can just not catch the exception at all. Repeat for as many
levels of function as necessary; I have programs where the only
except: blocks are in
main(). You can use
to handle a certain amount of per-function cleanup, or just rely on
Python's garbage collection to do things like close files for you when
an opened file object goes out of scope.
(This is the point where things like phase tracking come in handy so your top-level handler can tell the user what was going on when the error happened.)
Second, even if you need cleanup or error reporting in a function, you
can merge a run of code with the same exception handling needs into
except: block, instead of having N blocks that all do the
same thing. This approach of merging error handling will be familiar to
people who have read the Linux kernel source, and often results in a
function with only a single
try: that covers most of its code.
Sidebar: so what can you with an exception object?
The honest answer is 'not very much, unless the module documentation tells you otherwise'. Almost always the most sensible thing you can do with the exception object is turn it into a string and show it to the user.
(By 'exception object' I mean what you get in
e when you do '
MyException, e:' and then the exception happens.)
In theory modules could put additional information about the problem
into the exception object, so that you could do things like tell
different sorts of problems apart. In practice this information is
almost always encoded into a module's exception type hierarchy, so
that you usually have MyAuthException and MyConnectionException
exceptions (both of them being subclasses of MyException) instead of an
errorcause member of MyException.