Python catching entry exception with decimal.InvalidOperation - python

I'm working an exception example copied from a book.
from decimal import *
entry=input("GRD Conversion")
try:
grd_usd=Decimal(entry)
except decimal.InvalidOperation:
print("Invalid: ",entry)
Instead of the expected error of Invalid: 3d for 3d as my entry I get
Traceback (most recent call last): File
"/Users/myuser/Library/Preferences/IdeaIC2018.1/scratches/scratch.py",
line 6, in
grd_usd=Decimal(entry) decimal.InvalidOperation: []
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File
"/Users/myuser/Library/Preferences/IdeaIC2018.1/scratches/scratch.py",
line 7, in
except decimal.InvalidOperation: NameError: name 'decimal' is not defined
I'm using Python 3.6 on a Mac.

As the error suggests, you have nothing named decimal. When using from .. import *, the package itself does not become a variable. Then it is impossible to find decimal.InvalidOperation
import decimal
entry = input("GRD Conversion")
try:
grd_usd = decimal.Decimal(entry)
except decimal.InvalidOperation:
print("Invalid: ", entry)

Related

Python equivalent of Java Exception with cause

Is there a way in Python to raise an error that has another error as its cause?
In Java, you can create an instance of an exception with a cause such as in the following code
try {
throw new IOException();
} catch (IOException e) {
throw new RuntimeException("An exception occurred while trying to execute", e);
}
resulting in in this error message:
Exception in thread "main" java.lang.RuntimeException: An exception occurred while trying to execute
at thing.Main.main(Main.java:11)
Caused by: java.io.IOException
at thing.Main.main(Main.java:9)
Notice that the first exception (in the stack trace) is "caused by" the second.
This is, in my opinion, a great way to show an API user that a higher-level error occurred during a call, and the developer can debug it by looking at the lower-level exception which is the "cause" of the higher-level error (in this case the RuntimeException is caused by the IOException).
With the searches I've made, I haven't been able to find anything about having an error as the cause of another in Python. Can this be achieved in Python? How? And if not, what would be a Pythonic equivalent?
In Python it is achieved by a very similar structure:
try:
raise ValueError
except ValueError:
raise ValueError('second exception')
This will generate the following traceback:
Traceback (most recent call last):
File "main.py", line 2, in <module>
raise ValueError
ValueError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('second exception')
ValueError: second exception
Another Python feature is raise from which provide a slightly different traceback:
try:
raise ValueError
except ValueError as e:
raise ValueError('second exception') from e
Traceback:
Traceback (most recent call last):
File "main.py", line 2, in <module>
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('second exception') from e
ValueError: second exception
Further reading:
pep-3134
this SO answer

How to change the default message to a custom message in an Exception?

This might be a silly question, so I did some research on these questions:
How do I raise the same Exception with a custom message in Python?
Proper way to declare custom exceptions in modern Python?
But none of these are matches what I'm trying to do for my CLI script, namely:
1.) If a certain Exception is raised, I want to re-raise the same Exception, with a tailored message instead of the default one.
2.) I am not looking to redefine a custom Exception type, just the same Exception.
3.) I am not looking to print a console text. I want to actually raise Exception because I need the exit code to be as close as possible as if the original Exception was raised since another process relies on this code.
4.) I want the error to be as short as possible, straight and to the point. A full trace back is not necessary.
So for example, these are what I've tried:
Attempt 1:
def func():
try:
1/0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!")
Result 1:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 3, in <module>
1/0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 5, in <module>
raise ZeroDivisionError("Don't do that you numbnut!")
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 2.454 seconds
This meets my goal of #1, 2 and 3 are met, but the trace back is way too long... I don't need the original Exception at all, just the custom message.
Attempt 2:
def catch():
try:
1/0
return None
except ZeroDivisionError:
return ZeroDivisionError("Don't do that you numbnut!")
error = catch()
if error:
raise error
Result 2:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 10, in <module>
raise error
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 2.458 seconds
This gets me very close to what I want and is what I'm doing, however it feels quite unpythonic and pylint complains about the raise error line:
Raising NoneType while only classes or instances are allowed
pylint(raising-bad-type)
I also tried the methods in my linked questions, but they are unsatisfactory to all of my requirements as well. For the purpose of succinctness I have not included my attempts of those here.
My question is thus: is there a better, more obvious way to catch an Exception and re-raise it with a custom message that I'm simply missing?
This all seems quite unpythonic to me to begin with - but if it is really what you want why not raise from None in your first example in order not to get a larger traceback.
def func():
try:
1/0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!") from None
func()
Giving
Traceback (most recent call last):
File "/Users/dmodesitt/Dev/the.py", line 9, in <module>
func()
File "/Users/dmodesitt/Dev/the.py", line 6, in func
raise ZeroDivisionError("Don't do that you numbnut!") from None
ZeroDivisionError: Don't do that you numbnut!
This feature is called "chained exceptions" and was added in Python 3.
This block
try:
1 / 0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!")
>>>>
Traceback (most recent call last):
File "test123.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!")
ZeroDivisionError: Don't do that you numbnut!
Is similar to
try:
1 / 0
except ZeroDivisionError as e:
raise ZeroDivisionError("Don't do that you numbnut!") from e
=>>>>>>>>>
raceback (most recent call last):
File "test123.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 "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!") from e
ZeroDivisionError: Don't do that you numbnut!
But you can disable exceptions chaining using from None statement.
try:
1 / 0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!") from None
=>>>>>>>>
Traceback (most recent call last):
File "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!") from None
ZeroDivisionError: Don't do that you numbnut!
More information about the feature at https://www.python.org/dev/peps/pep-3134/
I was able to also use exit(...) to replicate very similar to what I want, but again it feels rather unpythonic:
def func():
try:
1/0
except ZeroDivisionError as err:
exit(f"{err.__class__.__name__}: Don't do that you numbnut!")
Result:
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 3.433 seconds
For the purpose of my script, I think this might be the simplest solution. But from a wider stand point, I believe the other answers are better.

Unable to catch exception: another exception occurred

I have the following code:
import pandas as pd
index = 2
timestamps = pd.date_range('2019-05-01', '2019-05-01')
try:
timestamp = timestamps[index]
except IndexError:
raise IndexError('index is out of timestamps.')
that results in the following being printed to the terminal
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/usr/local/lib/python3.7/site-packages/pandas/core/indexes/datetimes.py", line 1170, in __getitem__
result = self._data.__getitem__(key)
File "/usr/local/lib/python3.7/site-packages/pandas/core/arrays/datetimelike.py", line 426, in __getitem__
val = getitem(key)
IndexError: index 2 is out of bounds for axis 0 with size 1
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
IndexError: index is out of timestamps.
Why is IndexError: index is out of timestamps. simply not raised in this instance, please?
It was raised. The traceback provides info about the former error which was handled during the latter. Since you catch the exception and raise another, the traceback info includes both.
It is written in the description:
During handling of the above exception, another exception occurred:
You can try and raise another type of error and see that this is the error that was raised.
Because the raise statement in except block forces a new exception to handle the exception occurred in try block.
See python documentation

Raise an exception from a higher level, a la warnings

In the module warnings (https://docs.python.org/3.5/library/warnings.html) there is the ability to raise a warning that appears to come from somewhere earlier in the stack:
warnings.warn('This is a test', stacklevel=2)
Is there an equivalent for raising errors? I know I can raise an error with an alternative traceback, but I can't create that traceback within the module since it needs to come from earlier. I imagine something like:
tb = magic_create_traceback_right_here()
raise ValueError('This is a test').with_traceback(tb.tb_next)
The reason is that I am developing a module that has a function module.check_raise that I want to raise an error that appears to originate from where the function is called. If I raise an error within the module.check_raise function, it appears to originate from within module.check_raise, which is undesired.
Also, I've tried tricks like raising a dummy exception, catching it, and passing the traceback along, but somehow the tb_next becomes None. I'm out of ideas.
Edit:
I would like the output of this minimal example (called tb2.py):
import check_raise
check_raise.raise_if_string_is_true('True')
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise.raise_if_string_is_true(string)
RuntimeError: An exception was raised.
I can't believe I am posting this
By doing this you are going against the zen.
Special cases aren't special enough to break the rules.
But if you insist here is your magical code.
check_raise.py
import sys
import traceback
def raise_if_string_is_true(string):
if string == 'true':
#the frame that called this one
f = sys._getframe().f_back
#the most USELESS error message ever
e = RuntimeError("An exception was raised.")
#the first line of an error message
print('Traceback (most recent call last):',file=sys.stderr)
#the stack information, from f and above
traceback.print_stack(f)
#the last line of the error
print(*traceback.format_exception_only(type(e),e),
file=sys.stderr, sep="",end="")
#exit the program
#if something catches this you will cause so much confusion
raise SystemExit(1)
# SystemExit is the only exception that doesn't trigger an error message by default.
This is pure python, does not interfere with sys.excepthook and even in a try block it is not caught with except Exception: although it is caught with except:
test.py
import check_raise
check_raise.raise_if_string_is_true("true")
print("this should never be printed")
will give you the (horribly uninformative and extremely forged) traceback message you desire.
Tadhgs-MacBook-Pro:Documents Tadhg$ python3 test.py
Traceback (most recent call last):
File "test.py", line 3, in <module>
check_raise.raise_if_string_is_true("true")
RuntimeError: An exception was raised.
Tadhgs-MacBook-Pro:Documents Tadhg$
If I understand correctly, you would like the output of this minimal example:
def check_raise(function):
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def function():
1/0
check_raise(function)
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise(function)
RuntimeError: An exception was raised.
In fact, it's a lot more output; there is exception chaining, which could be dealt with by handling the RuntimeError immediately, removing its __context__, and re-raising it, and there is another line of traceback for the RuntimeError itself:
File "tb2.py", line 5, in check_raise
raise RuntimeError('An exception was raised.')
As far as I can tell, it is not possible for pure Python code to substitute the traceback of an exception after it was raised; the interpreter has control of adding to it but it only exposes the current traceback whenever the exception is handled. There is no API (not even when using tracing functions) for passing your own traceback to the interpreter, and traceback objects are immutable (this is what's tackled by that Jinja hack involving C-level stuff).
So further assuming that you're interested in the shortened traceback not for further programmatic use but only for user-friendly output, your best bet will be an excepthook that controls how the traceback is printed to the console. For determining where to stop printing, a special local variable could be used (this is a bit more robust than limiting the traceback to its length minus 1 or such). This example requires Python 3.5 (for traceback.walk_tb):
import sys
import traceback
def check_raise(function):
__exclude_from_traceback_from_here__ = True
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def print_traceback(exc_type, exc_value, tb):
for i, (frame, lineno) in enumerate(traceback.walk_tb(tb)):
if '__exclude_from_traceback_from_here__' in frame.f_code.co_varnames:
limit = i
break
else:
limit = None
traceback.print_exception(
exc_type, exc_value, tb, limit=limit, chain=False)
sys.excepthook = print_traceback
def function():
1/0
check_raise(function)
This is the output now:
Traceback (most recent call last):
File "tb2.py", line 26, in <module>
check_raise(function)
RuntimeError: An exception was raised.
EDIT: The previous version did not provide quotes or explanations.
I suggest referring to PEP 3134 which states in the Motivation:
Sometimes it can be useful for an exception handler to intentionally
re-raise an exception, either to provide extra information or to
translate an exception to another type. The __cause__ attribute
provides an explicit way to record the direct cause of an exception.
When an Exception is raised with a __cause__ attribute the traceback message takes the form of:
Traceback (most recent call last):
<CAUSE TRACEBACK>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
<MAIN TRACEBACK>
To my understanding this is exactly what you are trying to accomplish; clearly indicate that the reason for the error is not your module but somewhere else. If you are instead trying to omit information to the traceback like your edit suggests then the rest of this answer won't do you any good.
Just a note on syntax:
The __cause__ attribute on exception objects is always initialized
to None. It is set by a new form of the 'raise' statement:
raise EXCEPTION from CAUSE
which is equivalent to:
exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc
so the bare minimum example would be something like this:
def function():
int("fail")
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("An exception was raised.")
raise err from original_error
check_raise(function)
which gives an error message like this:
Traceback (most recent call last):
File "/PATH/test.py", line 7, in check_raise
function()
File "/PATH/test.py", line 3, in function
int("fail")
ValueError: invalid literal for int() with base 10: 'fail'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/PATH/test.py", line 12, in <module>
check_raise(function)
File "/PATH/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: An exception was raised.
However the first line of the cause is the statement in the try block of check_raise:
File "/PATH/test.py", line 7, in check_raise
function()
so before raising err it may (or may not) be desirable to remove the outer most traceback frame from original_error:
except Exception as original_error:
err = RuntimeError("An exception was raised.")
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
This way the only line in the traceback that appears to come from check_raise is the very last raise statement which cannot be omitted with pure python code although depending on how informative the message is you can make it very clear that your module was not the cause of the problem:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
The advantage to raising exception like this is that the original Traceback message is not lost when the new error is raised, which means that a very complex series of exceptions can be raised and python will still display all the relevant information correctly:
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
def test_chain():
check_raise(test)
def test():
raise ValueError
check_raise(test_chain)
gives me the following error message:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 16, in test
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 13, in test_chain
check_raise(test)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test_chain encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
Yes it is long but it is significantly more informative then:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
RuntimeError: An exception was raised.
not to mention that the original error is still usable even if the program doesn't end:
import traceback
def check_raise(function):
...
def fail():
raise ValueError
try:
check_raise(fail)
except RuntimeError as e:
cause = e.__cause__
print("check_raise failed because of this error:")
traceback.print_exception(type(cause), cause, cause.__traceback__)
print("and the program continues...")
I understand 'Don't do this'. On the other hand, there may be some special use cases i believe. I'm generating own errors (just deleting some defined frames...) this way
def get_traceback_with_removed_frames_by_line_string(lines):
"""In traceback call stack, it is possible to remove particular level defined by some line content.
Args:
lines (list): Line in call stack that we want to hide.
Returns:
string: String traceback ready to be printed.
"""
exc = trcbck.TracebackException(*sys.exc_info())
for i in exc.stack[:]:
if i.line in lines:
exc.stack.remove(i)
return "".join(exc.format())
I return just string.
If there is concrete function that is raising, you can add it to ignored frames.
Though have in mind, that if you hide something, somebody may not understand why is something happening...
My use case was to hide only first level - decorator from my library that was decorating all user functions in framework, so error from user side was on level 1.

Exception Passing In Python

I have a bit of code that does some functional exception handling and everything works well, exceptions are raised when I want them to be, but when I'm debugging, the line-traces don't always do quite what I want them to.
Example A:
>>> 3/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Example B:
>>> try: 3/0
... except Exception as e: raise e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
In both of these examples, the exception really occurs in line 1, where we attempt to do 3/0, but in the latter example, we are told it has occurred on line 2, where it is raised.
Is there a way in Python to raise an exception, as if it were another exception, something that would produce the following output:
>>> try: 3/0
... except Exception as e: metaraise(e)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
When you re-raise an exception that you caught, such as
except Exception as e: raise e
it resets the stack trace. It's just like re-raising a new exception. What you want is this:
except Exception as e: raise
For reference, the solution is approximately as follows:
def getException():
return sys.exc_info()
def metaraise(exc_info):
raise exc_info[0], exc_info[1], exc_info[2]
try: 3/0
except:
e = getException()
metaraise(e)
The beautiful part of this is that you can then pass around the variable e and metaraise it somewhere else, even if other exceptions have been encountered along the way.

Categories

Resources