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 try s 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.)