exception handling in complex Python programs

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Wed Aug 20 12:37:02 EDT 2008


dbpokorny at gmail.com a écrit :
> On Aug 19, 10:19 am, eliben <eli... at gmail.com> wrote:
> 
>> P.S. There's a common case where a method is passed a filename, to do
>> something with a file (say, read data). Should the method catch the
>> errors possibly thrown by open(), or leave it to the caller ?
> 
> You want to look up Easier to Ask Forgivness than Permission (EAFP)
> which is touted as the "canonical" error-handling paradigm for Python.
> This would give rise to the following function:
> 
>   def do_something(filename):
>     try:
>       f = open(filename)
>     except IOError:
>       return err("File %s not found" % filename)
>     ...
> 
> where err is a function that generates an error object that your
> application understands. 

Sorry but that's IMHO totally broken.

This "error object" is useless (heck, we *do* have exceptions, don't we 
???), *returning* it ruins the whole point of structured exception 
handling and take us back to infamous C error code checking (which are 
almost never checked), and - icing on the cake - the error message is 
very possibly wrong and misleading (IOError dont necessarily mean 'file 
not found'). This kind of exception "handling" manages to be worse than 
no exception handling at all.

> I personally think this is sloppy because you
> have to couple the exception type with the function --- between file()
> and open() in Python 2 and 3, a NameError is thrown with open() in
> Python 3 

??? I suspect this has nothing to do with any error happening while 
opening the file. NameError means the name doesn't exists in the current 
namespace nor it's enclosing namespaces. Could it be possible that 
open() has been removed from Py3k ?

> and an IOError is thrown in the other three cases <bashes
> head against keyboard>. The alternative is
> 
>   def do_something(filename):
>     if not os.access(filename,os.R_OK):
>       return err(...)
>     f = open(filename)
>     ...

This gets even worse. race condition... Things can change between the 
call to os.access and the call to open. Well-known antipattern.

> or, (and this last one I actually used for a web application)
> 
>   def do_something(filename):
>     if not os.access(filename,os.R_OK):
>       raise MyApplicationsExceptionType("File not found...")

You loose all the useful information you'd have from an IOError raised 
by a direct call to open...

>     f = open(filename)
>     ...


... IOError that you're still likely to see happen anyway.

> The last one has the advantage that you can write a request handler
> like this
> 
>   def handle_http_request(...):
>     func = specific_handler_func(...)
>     try:
>       response = func(...)
>       return response
>     except MyApplicationsExceptionType as exc: #3.0 syntax
>       return error_response(exc,...)


If you want to raise a different exception type - which can indeed be a 
sensible thing to do, depending on the context -, you can do it safely 
and keep accurate informations:

def do_something(filename):
     try:
         f = open(filename)
     except IOError, e
        raise MyApplicationsExceptionType(e.msg)
        # could even pass whole traceback etc
     # etc...


> Exceptions you don't expect (i.e. bugs)

An exception you don't expect is not necessarily a bug. Try unplugging 
your lan cable while writing to a socket connected to another computer...

(snip)

> If you are writing a library (for instance using a file for persistent
> storage), then the answer to your question is "don't catch the
> exception." Clients will expect the usual exception to be thrown when
> a bad file name is passed.

Indeed.



More information about the Python-list mailing list