From Lawrence Oluyede's blog entry about recent Python SVN updates:

urllib raises IOError if the servers response contains an invalid HTTP status line.

Augh. Wait, that's not strong enough, let me try again: AUGH.

In fact, it gets worse. To quote from the urrlib2 SVN source code's comment about its URLError error class:

URLError is a sub-type of IOError, but it doesn't share any of the implementation. [...]

(urllib2 also directly raises mis-formatted IOError exceptions for some things.)

Python's IOError and OSError have a very specific purpose; they report low level operating system errors. That is the only thing they should be used for, most especially if you are not going to carefully mimic the existing exception object format (which you can't do, because you don't have a real errno value). Having an alleged IOError object that looks nothing like real IOError objects is actively offensive, especially when it is in the standard library.

What these people have done, whether they realize it or not, is to turn IOError exception objects into nothing more than strings, because these modules have insured that there is no higher structure that you can count on an IOError instance having. Unfortunately the relevant Python documentation does not mention this, and continues to mislead the innocent into believing that when they catch an IOError exception they can do useful things like look at its errno or strerror attribute.

Or, to boil it down: never raise or subclass IOError, OSError, or EnvironmentError.

(This applies to EnvironmentError as a whole because using it in an except clause is the common way to catch both IOError and OSError at once.)

The only exception is if you are somehow directly calling a C library function (that is documented as setting errno on failures, which not everything does), in which case you should do the research to exactly mimic how the Python core code does it.

To my horror, urllib2 is hardly the only offender; there are even some in C level modules (in bz2module.c and zipimport.c). But that doesn't excuse them making the situation worse; in fact they should be moving towards making the situation better. Even with backwards compatibility concerns they could have raised URLError instead of IOError directly (or taken the opportunity to introduce a proper error class if they felt URLError was inapplicable).

(This rant has been brought to you by my attempt to catch up on Planet Python.)