Python only print traceback of raised exception - python

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

Related

How to suppress irrelevant errors in a try block

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.

How can I swallow python exception messages?

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")

Python: How to catch inner exception of exception chain?

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

rethrowing python exception. Which to catch?

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

Python print last traceback only?

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

Categories