Many programmers have had it drilled into their head that exceptions, in any language, should only be used in truly exceptional cases. They’re wrong. The Python community’s approach to exceptions leads to cleaner code that’s easier to read. And that’s without the monstrous hit to performance commonly associated with exceptions in other languages.
There’s a nice point made about exceptions vs error codes on the c2 wiki:
If you don’t explicitly handle an exception, Something Bad Happens, and that is a Good Thing. Consider the consumer and producer of some facility – in the simplest case, one method calling another. If the consumer is written by a conscientious programmer, then s/he will check the return value / catch the exception (or knowingly allow the exception to bubble higher). In this case, the overall system works well, no matter if the facility producer (the method called) throws an exception / returns an error condition or not.
However, if the caller is written sloppily, or quickly, it is unlikely that the caller will check the return value / catch the exception (when appropriate). If you use return values, the error is simply lost, and the internal logical consistency of your system has just been – silently! – destroyed. Your system may continue to do something useful, but most likely, it will fail in a subtle, hard-to-diagnose fashion.