I have the following two functions:
>>> def spam():
... raise ValueError('hello')
...
>>> def catch():
... try:
... spam()
... except ValueError:
... raise ValueError('test')
Trying to catch the second ValueError exception works just fine and prints the exception's error message:
>>> try:
... catch()
... except ValueError as e:
... print(e)
...
test
Is there however any way to access the original exception's error message (i.e. 'hello')? I know I can print the full traceback with:
>>> try:
... catch()
... except ValueError as e:
... import traceback
... print(traceback.format_exc())
...
Traceback (most recent call last):
File "<stdin>", line 3, in catch
File "<stdin>", line 2, in spam
ValueError: hello
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 5, in catch
ValueError: test
but I don't exactly want to parse the hello from that string. Is there a way to access the list of exceptions and their respective messages, from which I would simply take the first one?
Figured it out: the original exception is available via e.__cause__.
Related
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.
I am currently using pytesseract which defines an exception class as follows:
class TesseractError(Exception):
def __init__(self, status, message):
self.status = status
self.message = message
self.args = (status, message)
in my main.py, I tried several ways to use this exception for exception handling but I've gotten no "TesseractError" attribute error and no "TesseractError" defined error.
1.
>>> from pytesseract import TesseractError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'TesseractError'
2.
>>> import pytesseract
>>> try: raise pytesseract.TesseractError(True,"True")
... except TesseractError: print("error")
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'pytesseract' has no attribute 'TesseractError'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'TesseractError' is not defined
3.
>>> import pytesseract
>>> try: raise TesseractError(True,"True")
... except TesseractError: print("error")
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'TesseractError' is not defined
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'TesseractError' is not defined
However, when I try the following in terminal, it just works.
>>> class ERR(Exception): pass
>>> try: raise ERR()
... except ERR: print("error found")
error found
so it seems it's the importing step is wrong but I don't know what caused it.
Here's the __init__ file:
https://github.com/madmaze/pytesseract/blob/master/src/init.py
Notice that it does not import the TesseractError to be visible from the package itself. You could either change the init file or import from the package>module>
from pytesseract.pytesseract import TesseractError
On executing below code I get below error if it fails to get firefox profile/webdriver for some reason:
exceptions must be old-style classes or derived from BaseException, not NoneType
I want to understand why this error is displayed in this case:
self.error = 0
self.profile, profileErrStatus = self.GetFireFoxProfile(path)
if self.profile:
self.driver, driverErrStatus = self.GetFireFoxWebDriver(self.profile)
if self.driver:
else:
print('Failed to get Firefox Webdriver:%s'%(str(sys.exc_info()[0])))
raise
else:
print('Failed to get Firefox Profile:%s'%(str(sys.exc_info()[0])))
raise
This is because you are using raise without providing an exception type or instance.
According to the documentation:
The sole argument to raise indicates the exception to be raised. This
must be either an exception instance or an exception class (a class
that derives from Exception).
Demo:
>>> raise
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
>>> raise ValueError('Failed to get Firefox Webdriver')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Failed to get Firefox Webdriver
Note that raise without arguments can be used inside an except block to re-raise an exception.
FYI, on python3, it would raise a RuntimeError instead:
>>> raise
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: No active exception to reraise
Note that raise without an argument is allowed if you are in a catch block with an exception currently handled:
If you need to determine whether an exception was raised but don’t intend to handle it, a simpler form of the raise statement allows you to re-raise the exception:
>>> try:
... raise NameError('HiThere')
... except NameError:
... print 'An exception flew by!'
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere
(From Raising Exceptions in the documentation.)
Beware, though, that if a method called in the expect block clears the exception info, raise without an argument will result in the exceptions must be… exception again. So explicitly assigning the exception to a variable with except … as is safer:
try:
raise NameError('HiThere')
except NameError as e:
log_and_clear_exception_info('An exception flew by!')
raise e
I have a doctest which expects an IOError when a file is not found.
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: /homes/ndeklein/workspace/MS/PyMS/conffig.ini
However, if I want to test this from a different pc, or someone else wants to test it, the path isn't going to be /homes/ndeklein/workspace/MS/PyMS/. I would want to do
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: os.path.abspath(conffig.ini)
but because it is in the docstring it sees os.path.abspath( as part of the result.
How can I make the result of the docstring test variable?
Do you actually need to match against the pathname? If not then just use ellipsis to skip that part of the output:
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: ...
If you do then you'll need to catch the error and test the value by hand. Something like:
>>> try:
... configParser('conffig.ini') # should not exist
... except IOError as e:
... print('ok' if str(e).endswith(os.path.abspath('conffig.ini')) else 'fail')
ok
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.