Suppose I want to check that a certain entry is in a Series. I would like to try to access that entry, and if that fails, raise a simple, short ValueError.
For example, I have a series that doesn't have entry C - I want a check to halt the script. Example:
s = {'A': 1, 'B': 2}
s = pd.Series(s)
try:
s['C']
except:
raise ValueError('C is missing.')
But this code throws a long KeyError before spitting out the ValueError. It works, but is verbose.
(I know that I can use an assert statement instaead.)
Why doesn't the try block suppress the KeyError - isn't that part of its purpose? Is there a way to get my intended behavior?
You are seeing exception chaining. This extra information can be suppressed with a from None clause in your raise statement. Consider this (totally contrived) case where I am suppressing a ZeroDivisionError and raising a KeyError:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise KeyError
...
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 4, in <module>
KeyError
But if I use from none:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise KeyError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
KeyError
>>>
Also note, you really should not use a bare except clause. Catch as specific an error as possible.
Related
https://docs.python.org/3/tutorial/errors.html#exception-chaining
>>>
>>> def func():
... raise IOError
...
>>> try:
... func()
... except IOError as exc:
... raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
OSError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database
Exception chaining happens automatically when an exception is raised inside an except or finally section. So what is the purpose of using the from exc in raise RuntimeError('Failed to open database') from exc? Isn't it implicit?
You can always check the PEP which introduces a feature. In this case, you may read this section of PEP 3141.
The use of the syntax raise EXCEPTION from CAUSE allows for an explicit chaining of exceptions. Like in the example given on that page, if you want to raise only a certain error in an API, but also want to highlight the original exception raising the issue, you can use this way of chaining exceptions.
So it just offers a more explicit way of doing that, while the "normal" syntax raise EXCEPTION only implicitly chains the exceptions.
Is this understandable? Let me know if you don't understand it.
How to test a 'multi traceback' using doctest?
It seems that using several ELLIPSIS and <BLANKLINE> won't do the trick:
def myfunc():
"""
>>> myfunc()
Traceback (most recent call last):
...
ValueError: this is
<BLANKLINE>
The above exception was the direct cause of the following exception:
<BLANKLINE>
Traceback (most recent call last):
...
TypeError: it
"""
try:
raise ValueError('this is')
except ValueError as err:
raise TypeError('it') from err
import doctest
doctest.testmod(optionflags=doctest.REPORT_NDIFF|doctest.ELLIPSIS)
Result:
"test.py" 23L, 490C written
**********************************************************************
File "test.py", line 4, in __main__.myfunc
Failed example:
myfunc()
Differences (ndiff with -expected +actual):
Traceback (most recent call last):
- ...
+ File "test.py", line 17, in myfunc
+ raise ValueError('this is')
ValueError: this is
<BLANKLINE>
The above exception was the direct cause of the following exception:
<BLANKLINE>
Traceback (most recent call last):
- ...
+ File "/usr/lib/python3.7/doctest.py", line 1329, in __run
+ compileflags, 1), test.globs)
+ File "<doctest __main__.myfunc[0]>", line 1, in <module>
+ myfunc()
+ File "test.py", line 19, in myfunc
+ raise TypeError('it') from err
TypeError: it
**********************************************************************
1 items had failures:
1 of 1 in __main__.myfunc
***Test Failed*** 1 failures.
But if I squash all, it will pass:
>>> myfunc()
Traceback (most recent call last):
...
TypeError: it
I'm afraid that is not possible to check "multi tracebacks" in that way.
The problem is that doctest ignores everything except the exception class and its message.
In your example, it will be just:
TypeError: it
If you are interested in how this works, check the doctest.py and search for
exc_msg = traceback.format_exception_only(*exception[:2])[-1]
That "exc_msg" will only contain the raised exception's details:
TypeError: it
Alternatives
If it is possible, you could change your test to not raise any exception but print the wanted message.
Another possibility could be use another "doctest engine" like byexample. It works in the same way that doctest does but it's more flexible (quick overview here).
If you have a lot of tests, you may want to try its compatibility mode with doctest to avoid rewriting everything.
For your example, this should be:
"""
>>> from your_module import myfunc
"""
def myfunc():
"""
>>> myfunc()
Traceback (most recent call last):
...
ValueError: this is
<BLANKLINE>
The above exception was the direct cause of the following exception:
<BLANKLINE>
Traceback (most recent call last):
...
TypeError: it
"""
try:
raise ValueError('this is')
except ValueError as err:
raise TypeError('it') from err
And to run it you do from the shell:
byexample -l python -o '+py-doctest -py-pretty-print +ELLIPSIS' your_module.py
Disclaimer: I'm the author of byexample. I'm a really fan of doctest but I know that has its limitations and checking exceptions is one of them (specially if you work in a dual Python 2.x / 3.x project).
For that reason I created byexample: It is really useful to me and I really hope that it would be useful to others.
Ask me any question that you have, here or in github
Consider the simple example:
def f():
try:
raise TypeError
except TypeError:
raise ValueError
f()
I want to catch TypeError object when ValueError is thrown after f() execution. Is it possible to do it?
If I execute function f() then python3 print to stderr all raised exceptions of exception chain (PEP-3134) like
Traceback (most recent call last):
File "...", line 6, in f
raise TypeError
TypeError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "...", line 11, in <module>
f()
File "...", line 8, in f
raise ValueError
ValueError
So I would get the list of all exceptions of exception chain or check if exception of some type (TypeError in the above example) exists in exception chain.
Python 3 has a beautiful syntactic enhancement on exceptions handling. Instead of plainly raising ValueError, you should raise it from a caught exception, i.e.:
try:
raise TypeError('Something awful has happened')
except TypeError as e:
raise ValueError('There was a bad value') from e
Notice the difference between the tracebacks. This one uses raise from version:
Traceback (most recent call last):
File "/home/user/tmp.py", line 2, in <module>
raise TypeError('Something awful has happened')
TypeError: Something awful has happened
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/user/tmp.py", line 4, in <module>
raise ValueError('There was a bad value') from e
ValueError: There was a bad value
Though the result may seem similar, in fact it is rather different! raise from saves the context of the original exception and allows one to trace all the exceptions chain back - which is impossible with simple raise.
To get the original exception, you simply have to refer to new exception's __context__ attribute, i.e.
try:
try:
raise TypeError('Something awful has happened')
except TypeError as e:
raise ValueError('There was a bad value') from e
except ValueError as e:
print(e.__context__)
>>> Something awful has happened
Hopefully that is the solution you were looking for.
For more details, see PEP 3134 -- Exception Chaining and Embedded Tracebacks
I'm learning to use python. I just came across this article:
http://nedbatchelder.com/blog/200711/rethrowing_exceptions_in_python.html
It describes rethrowing exceptions in python, like this:
try:
do_something_dangerous()
except:
do_something_to_apologize()
raise
Since you re-throw the exception, there should be an "outer catch-except" statement. But now, I was thinking, what if the do_something_to_apologize() inside the except throws an error. Which one will be caught in the outer "catch-except"? The one you rethrow or the one thrown by do_something_to_apologize() ?
Or will the exception with the highest priotiry be caught first?
Try it and see:
def failure():
raise ValueError, "Real error"
def apologize():
raise TypeError, "Apology error"
try:
failure()
except ValueError:
apologize()
raise
The result:
Traceback (most recent call last):
File "<pyshell#14>", line 10, in <module>
apologize()
File "<pyshell#14>", line 5, in apologize
raise TypeError, "Apology error"
TypeError: Apology error
The reason: the "real" error from the original function was already caught by the except. apologize raises a new error before the raise is reached. Therefore, the raise in the except clause is never executed, and only the apology's error propagates upward. If apologize raises an error, Python has no way of knowing that you were going to raise a different exception after apologize.
Note that in Python 3, the traceback will mention both exceptions, with a message explaining how the second one arose:
Traceback (most recent call last):
File "./prog.py", line 9, in <module>
File "./prog.py", line 2, in failure
ValueError: Real error
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "./prog.py", line 11, in <module>
File "./prog.py", line 5, in apologize
TypeError: Apology error
However, the second exception (the "apology" exception) is still the only one that propagates outward and can be caught by a higher-level except clause. The original exception is mentioned in the traceback but is subsumed in the later one and can no longer be caught.
The exception thrown by do_something_to_apologize() will be caught. The line containing raise will never run, because of the exception thrown by do_something_to_apologize. Also, I don't believe there is any idea of "priority" in python exceptions.
I believe a better idea is to use
raise NewException("Explain why") from CatchedException
pattern. In particular, considering Python 3 and the example given by #BrenBarn I use following
def failure():
raise ValueError("Real error")
try:
failure()
except ValueError as ex:
raise TypeError("Apology error") from ex
which yields
--------- ValueError----
Traceback (most recent call last)
4 try:
----> 5 failure()
6 except ValueError as ex:
1 def failure():
----> 2 raise ValueError("Real error")
3
ValueError: Real error
The above exception was the direct cause of the following exception:
-----TypeError-----
Traceback (most recent call last)
5 failure()
6 except ValueError as ex:
----> 7 raise TypeError("Apology error") from ex
TypeError: Apology error
Consider the following code and traceback:
>>> try:
... raise KeyboardInterrupt
... except KeyboardInterrupt:
... raise Exception
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception
>>>
I'd like to print only the most recent traceback (the one in which Exception was raised).
How can this be achieved?
From the above example, I'd like to print the following, as if raise Exception had been called outside the except clause.
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception
The perfect question for me.
You can suppress the exception context, that is the first part of the traceback, by explicitly raising the exception from None:
>>> try:
raise KeyboardInterrupt
except:
raise Exception from None
Traceback (most recent call last):
File "<pyshell#4>", line 4, in <module>
raise Exception from None
Exception
This was formalized in PEP 409 and further improved in PEP 415. The original bug request for this was filed by myself btw.
Note that suppressing the context will not actually remove the context from the new exception. So you can still access the original exception:
try:
try:
raise Exception('inner')
except:
raise Exception('outer') from None
except Exception as e:
print(e.__context__) # inner