This is kind of weird (Python 2.7.3):
try:
print "hello"
except foo:
print "foo"
prints "hello". The problem (IMHO) is that apparently the except clause
doesn't get evaluated until after some exception is caught. Which means
it never notices that foo is not defined until it's too late.
This is exactly the same as, well, everything in Python. Nothing is
evaluated until needed.
Consider this piece of legal Python code:
Err = None
if condition(x) > 100:
Err = OneException
elif another_condition(x):
Err = AnotherException
try:
spam(a, b, c)
except Err:
recover()
And consider that spam() may very well set the global Err to yet another
value, or delete it altogether, before raising. How should Python check
that Err is defined to an actual exception ahead of time?
The names of exceptions are no different from any other names in Python.
They're merely names, and they're looked up at runtime. If you want to
boggle/confuse/annoy your friends, shadowing builtins can help:
py> def spam(x):
.... global UnicodeEncodeError
.... UnicodeEncodeError = ZeroDivisionError
.... return 1/x
....
py> try:
.... spam(0)
.... except UnicodeEncodeError:
.... print("Divided by zero")
....
Divided by zero
(By the way, if you ever do this by accident, you can recover with a
simple "del UnicodeEncodeError" to restore access to the builtin.)
As Terry has said, those sort of pre-runtime checks are the
responsibility of pylint or pychecker or equivalent. Python is too
dynamic to allow the sort of compile-time checks you can get from a
Haskell, C or Pascal, so the Python compiler delegates responsibility to
third-party applications. There's no point in making an exceptions for
exceptions.
This just came up in some code, where I was trying to catch a very rare
exception. When the exception finally happened, I discovered that I had
a typo in the except clause (I had mis-spelled the name of the
exception). So, instead of getting some useful information, I got an
AttributeError :-(
One of the nice features of Python 3:
py> try:
.... 1/0
.... except ZeroDividingError:
.... pass
....
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'ZeroDividingError' is not defined
Very useful for debugging unexpected errors in except clauses. The
downside is that until Python 3.3, there's no way to turn it off when you
intentionally catch one exception and raise another in it's place.
Is this a bug, or intended behavior? It seems to me it would be much
more useful (if slightly more expensive) to evaluate the names of the
exceptions in the expect clause before running the try block.
"Slightly" more expensive? Methinks you are underestimating the level of
dynamism of Python.
py> def pick_an_exception():
.... global x
.... if 'x' in globals():
.... return TypeError
.... x = None
.... return NameError
....
py> for i in range(3):
.... try:
.... print(x + 1)
.... except pick_an_exception() as err:
.... print("Caught", err)
....
Caught name 'x' is not defined
Caught unsupported operand type(s) for +: 'NoneType' and 'int'
Caught unsupported operand type(s) for +: 'NoneType' and 'int'
The except expression can be arbitrarily expensive, with arbitrary side-
effects.