Modules should never raise core Python exceptions

September 6, 2016

Today I discovered that the zipfile module raises RuntimeError if you ask it to read a file from an encrypted ZIP archive and do not give it the right password (or don't give it a password at all). I wasn't happy. Apparently it isn't obvious to the people who maintain Python's standard library (and who updated it for Python 3) why this is a terrible idea (not that I'm surprised about that).

Here is a simple rule for module writers:

Your module should never raise an exception that the interpreter can raise for plain Python code.

The reason is simple, as illustrated here. RuntimeError can be raised by the Python interpreter under various circumstances, if various things go sufficiently badly wrong. Since the zipfile module raises it too, if you are trying to do something with a ZIP archive that may or may not be encrypted, you must use an over-broad try:

try:
  filedata = zarc.read(somefile)
except RuntimeError as e:
  ....

When that RuntimeError exception fires, how do you know whether the ZIP archive is encrypted, whether the zipfile module has a bug that triggers a RuntimeError from Python, or whether the the Python interpreter ran into an internal runtime error? The answer is that you basically don't. You have to cross your fingers and assume. It's probably an encrypted zipfile, so let's go with that.

(Life would be slightly better if the zipfile module had an interface that told you whether files in a ZIP archive were encrypted or not, but of course it doesn't have a documented way to do that. It doesn't even have an internal constant for the flag bit involved.)

If you have to put additional code inside the try, things get worse for obvious reasons; the more code (either yours or other people's) that's getting called, the more chances that bugs in that code will be the cause of RuntimeError or the like, instead of the exception from the zipfile module that you expect to be the only thing raising that error. We've seen variants on this before, and they can definitely trip you up. Probably not very often, but even one own goal here is more than enough.

This is why modules should never deliberately raise core Python exceptions, exceptions that can be raised by just running plain code. Catching them forces me and you to write overly broad trys because these exceptions are intrinsically broad. When you make up your own exceptions, you insure that they cover exactly and only the errors they should cover, not your errors plus some random set of other ones.

(Yeah, I've sort of said this before.)

PS: The exception to this rule is built-in exceptions that are specifically designed to be raised by user code as a standard signal of problems. An example is NotImplementedError, and it may be the only one that I really agree with.

Written on 06 September 2016.
« Using Magit to selectively discard changes in your git working tree
Why my smartphone is going to be an iPhone »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Sep 6 23:22:13 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.