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
Related
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.
I'm raising a new exception in try-except block with additional message. The original exception traceback is therefore not needed anymore. Is there any way to remove the original traceback and only print the traceback of the newly raised exception?
Example Code (Python 3.6.10):
try:
10/0
except:
raise Exception('some error')
Output:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
d:\xxx\main.py in
1 try:
----> 2 10/0
3 except:
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Exception Traceback (most recent call last)
d:\xxx\main.py in
2 10/0
3 except:
----> 4 raise Exception('some error')
Exception: some error
Desired output:
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
d:\xxx\main.py in
2 10/0
3 except:
----> 4 raise Exception('some error')
Exception: some error
I'm raising a new exception in try-except block with additional message. The original exception traceback is therefore not needed anymore.
You can discard the original exception, but I would reconsider that decision. The reason exception causes and contexts were added in Python 3 is because information about the original exception and stack trace is useful. I'd explicitly mark the original exception as the cause of the new exception, which changes the message a bit:
try:
1/0
except ZeroDivisionError as e:
raise Exception("Oh crud") from e
Output:
Traceback (most recent call last):
File "main.py", line 2, in <module>
1/0
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise Exception("Oh crud") from e
Exception: Oh crud
That said, if you really want to suppress information about the original exception, you can use None as the cause of the new exception:
try:
1/0
except ZeroDivisionError:
raise Exception("Oh crud") from None
Output:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise Exception("Oh crud") from None
Exception: Oh crud
Use with_traceback
import sys, traceback
try:
10/0
except Exception as exc:
raise exc.with_traceback(None)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-31-d77f0aded0d7> in <module>()
3 10/0
4 except Exception as exc:
----> 5 raise exc.with_traceback(None)
ZeroDivisionError: division by zero
If you just want to show it:
import sys, traceback
try:
10/0
except Exception:
ex_type, ex, tb = sys.exc_info()
traceback.print_tb(tb)
File "<ipython-input-4-1283199eb169>", line 3, in <module>
10/0
ALTERNATIVE
import sys, traceback
try:
10/0
except Exception as exc:
tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
for i in tb_str: print(i)
Traceback (most recent call last):
File "<ipython-input-17-3bc95dc2ebf5>", line 3, in <module>
10/0
ZeroDivisionError: division by zero
I have some code thus:
try:
subprocess.check_call(
"mysqldump {} {}".format(mysql_args, cmd), shell=True
)
except Exception as e:
raise Exception("Command failed")
The problem is that Exception logging code elsewhere in the application catches this exception and helpfully prints it out - which in this case looks like this:
Traceback (most recent call last):
File "/apps/django/myapp/tasks.py", line 336, in _safe_mysqldump
"mysqldump {} {}".format(mysql_args, cmd), shell=True
File "/usr/lib/python3.6/subprocess.py", line 291, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError:
Command 'mysqldump -ufoo -pMYSECRETPASSWORD myappdb t1 t2 t3...' returned non-zero exit status 6.
During handling of the above exception, another exception occurred:
etc.
The critical thing being it printed out the mysql connection string. How can I prevent it from doing this?
Use the syntax:
raise Exception("Command failed") from None
See PEP 409 Suppressing exception context):
>>> try:
... raise TypeError('a')
... except TypeError:
... raise ValueError('b') from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: b
Versus the default behaviour which you saw:
>>> try:
... raise TypeError('a')
... except TypeError:
... raise ValueError('b')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: a
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: b
As an alternative specific to your use case: do not use check_call which raises an exception but use subprocess.run
instead:
if subprocess.run(<command>).returncode != 0:
raise Exception("Command failed")
or subprocess.call
if subprocess.call(<command>) != 0:
raise Exception("Command failed")
You may change the output like this:
failed = False
try:
...
except Exception:
failed = True
finally:
if failed:
raise Exception("Command failed")
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
I have a traceback print and want to customize the last part of it.
What: The error occurred in another process and traceback lies there (as is the case in multiprocessing).
Problem: I want to have the full traceback and error report.
Similar to this code:
>>> def f():
g()
>>> def g():
raise Exception, Exception(), None ## my traceback here
>>> f()
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
f()
File "<pyshell#8>", line 2, in f
g()
File "<pyshell#11>", line 2, in g
raise Exception, Exception(), None ## my traceback starts here
my traceback appears here
my traceback appears here
Exception
Impossible "Solutions": subclass and mock-object
>>> from types import *
>>> class CostomTB(TracebackType):
pass
Traceback (most recent call last):
File "<pyshell#125>", line 1, in <module>
class CostomTB(TracebackType):
TypeError: Error when calling the metaclass bases
type 'traceback' is not an acceptable base type
>>> class CostomTB(object):
pass
>>> try: zzzzzzzzz
except NameError:
import sys
ty, err, tb = sys.exc_info()
raise ty, err, CostomTB()
Traceback (most recent call last):
File "<pyshell#133>", line 5, in <module>
raise ty, err, CostomTB()
TypeError: raise: arg 3 must be a traceback or None
I am using python 2.7.
I guess you want the full traceback stack. See this which is having very good examples python logging module.
If some confusion comes See the logging documentation.
You mentioned a separate process: if your problem is to capture the traceback in process A and show it in process B, as if the exception was actually raised in the latter, then I'm afraid there is no clean way to do it.
I would suggest to serialize the traceback in process A, send it to process B and from there raise a new exception that includes the former in its description. The result is a somewhat longer output, but it carries information about both processes stacks.
In the following example there aren't really two separate processes, but I hope it makes my point clearer:
import traceback, StringI
def functionInProcessA():
raise Exception('Something happened in A')
class RemoteException(Exception):
def __init__(self, tb):
Exception.__init__(self, "Remote traceback:\n\n%s" % tb)
def controlProcessB():
try:
functionInProcessA()
except:
fd = StringIO.StringIO()
traceback.print_exc(file=fd)
tb = fd.getvalue()
raise RemoteException(tb)
if __name__ == '__main__':
controlProcessB()
Output:
Traceback (most recent call last):
File "a.py", line 20, in <module>
controlProcessB()
File "a.py", line 17, in controlProcessB
raise RemoteException(tb)
__main__.RemoteException: Remote traceback:
Traceback (most recent call last):
File "a.py", line 12, in controlProcessB
functionInProcessA()
File "a.py", line 4, in functionInProcessA
raise Exception('Something happened in A')
Exception: Something happened in A