2009-08-20
Python modules should not reinvent OSError
In light of yesterday's entry, here is one of my new rules for Python modules, especially extension modules written in C:
If you're going to raise an exception because a system call failed, don't make up a new exception for it. Raise either
IOError
orOSError
themselves.
Every deviation from this causes annoyances
for Python programmers. This is especially visible in the situation
with signals and EINTR
, where I really do not want to
be rewriting the same code over and over again with different exception
classes (or worse, writing different code because you decided to hide
errno
somewhere new in your exception).
A corollary to this is that if for some reason you absolutely have
to raise a new exception, you should make it duck typing compatible
with OSError
(see here
for what that requires). But really, you don't have to, especially
because Python code can raise real OSError
exceptions.
(Please do not get creative with the error message for OSError
instances that you create yourself in Python code. If it is not
exactly the strerror()
of the errno
, you are doing it wrong.)
Extension modules written in C have no excuse,
because the Python C API makes it very easy to do
the right thing (and reusing OSError
saves you from having to create
your own C level exception object). Alas, the standard Python exception
modules are just overflowing with bad examples, where people make up a
new exception instead of reusing OSError
or IOError
.
(This is sort of the reverse corollary of not using IOError
for things that aren't system call errors.)
Sidebar: to wrap or not to wrap
I'm aware that this looks inconsistent with my views on not wrapping
exceptions. The difference to me is if you are
essentially wrapping a system call and directly exposing errno
,
you should be using the standard way of doing that, which is an
EnvironmentError exception (or should be). If the system call's failure
is just an internal implementation detail, you should wrap the problem
up in your own exception to expose the high level issue.
(This is the difference between 'cannot resolve hostname, nameserver
not available' and 'sendto()
failed'. A DNS module that returned the
latter would be technically correct but not useful.)