Why 'raise EXCEPTION from CAUSE', not 'raise (EXCEPTION from CAUSE)'? [closed] - python

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
Let me elaborate on the mildly cryptic question title.
In Python 3 we've got neat exception chaining feature, that lets you add more context to exceptions in the process of their propagation.
Recently, I wanted to attach extra exception information to the exception, but without throwing it straightaway. The code was doing some defensive processing in generator, so I wanted to yield it instead, so I tried something like:
def gen():
for row in csv_file:
try:
yield parse(row)
except Exception as e:
yield RuntimeError(f"Bad row: {row}") from e
To my disappointment, that didn't work! Turned out that raise EXCEPTION from CAUSE is a compound operator as PEP-3134 explains.
To get around this in my code, I set __cause__ = e manually and carried on. However, it still bothered me why it was implemented that way.
Imagine instead we had an operator from with the following syntax: EXCEPTION from CAUSE, doing exactly what raise .. from .. is doing, except throwing, and returning new Exception instead. That way:
it's compatible, you can still use raise .. from .. syntax (now internally it'd be parsed as raise (.. from ..))
it would make everything more composable, allowing to attach cause without necessarily throwing (in particular my usecase). I can that it's a bit unusual use of exceptions though.
presumably, that would also make implementation simpler, since you'd not need to modify and clash exiting primitive (simple raise operator). I see it as a main argument for my 'alternative form' of the operator from the perspective of Python language developer.
I've gone through PEP/googled but hasn't really found any rationale. I experimented to check if there is something else apart from __cause__ being set, but it doesn't seem so. I'm attaching bits of code that I used to check that:
def failing():
raise RuntimeError("function failed!")
def cause_from():
try:
failing()
except Exception as e:
raise RuntimeError("extra info") from e
def cause_manual():
try:
failing()
except Exception as e:
ee = RuntimeError("extra info")
ee.__cause__ = e
raise ee
It results in the same traceback:
$ python3 -c 'import exc; exc.cause_from()'
Traceback (most recent call last):
File "/tmp/exc.py", line 7, in cause_from
failing()
File "/tmp/exc.py", line 2, in failing
raise RuntimeError("function failed!")
RuntimeError: function failed!
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/exc.py", line 9, in cause_from
raise RuntimeError("extra info") from e
RuntimeError: extra info
$ python3 -c 'import exc; exc.cause_manual()'
Traceback (most recent call last):
File "/tmp/exc.py", line 13, in cause_manual
failing()
File "/tmp/exc.py", line 2, in failing
raise RuntimeError("function failed!")
RuntimeError: function failed!
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/exc.py", line 17, in cause_manual
raise ee
RuntimeError: extra info
The only difference in traceback is the line where exception was thrown (raise RuntimeError("extra info") from e vs raise ee). The cause_from version looks somewhat more readable.
So it's the only reason I see for that choice of syntax structure, I wonder if it's also the intended motivation? Is it possible that I miss some subtle effect of raise .. from .. as a compound operator?

Related

what is the purpose of using the `from exc` in `raise RuntimeError('Failed to open database') from exc`?

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.

Raise an exception with traceback starting from caller

I'm trying to make an automated test framework for a side-project and could use some help creating the assertion checks.
Running this in python...
assert(False)
Gives you this...
Traceback (most recent call last):
File "test.py", line 1, in <module>
assert(False)
AssertionError
As you can see the traceback lowest level is assert(False). So I made my custom assert that prints when the assert succeeds.
def custom_assert(condition):
if condition:
print("Yay! It werks!")
else:
raise Exception("Nay, it don't werks...")
custom_assert(False)
But instead of what assert gives, custom_assert gives me this.
Traceback (most recent call last):
File "test.py", line 14, in <module>
custom_assert(False)
File "test.py", line 12, in custom_assert
raise Exception("Nay, it don't werks...")
Exception: Nay, it don't werks...
Which is of course the default behavior. Perfectly useful 99.9999% of the time, but this is that one time it could be improved. It's not useful to know that the method I called to raise an error when the condition is false raised the error.
How can I make my custom_assert raise an exception with a traceback starting from the caller, the same way assert does?
P.S.: I don't want to print it, I want the exception to have properly modified traceback so it works properly with debuggers and other tools too!
Edit
To clarify, the traceback I want would be like this.
Traceback (most recent call last):
File "test.py", line 14, in <module>
custom_assert(False)
Exception: Nay, it don't werks...
Essentially what you want to do is something similar to this:
tb = None
try:
raise Exception('foo')
except Exception:
tb = sys.exc_info()[2]
tb.tb_frame = tb.tb_frame.f_back # This line doesn't work
raise Exception('Nay it doesnt werks').with_traceback(tb)
but you can't assign tb_frame, and from mucking around in the CPython code, this is C-generated data structures (not python) (see sys._getframe())
So your only option left is to mock the entire machinery and then convince python to use your stack. This looks like what jinja2 is doing. If that's what you choose to do, good luck! (It's out of my scope at that point)

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

Nested causes in nested exceptions in python [duplicate]

This question already has answers here:
"Inner exception" (with traceback) in Python?
(9 answers)
Closed 8 years ago.
Is there a way to provide information about the cause of an inner exception when passing it up the chain (like it's possible in java with the cause property of Exception class).
Please consider the following "python pseudo code" (without 100 % correct and invented function and class names)
try:
clientlib.receive_data_chunk()
except ClientException as clientException:
raise RuntimeError("reading from client failed"
+ " (see nested exceptions for details)", cause=clientException)
and in clientlib.py
def receive_data_chunk():
try:
chunk = socket.read(20)
return chunk
except IOException as iOException:
raise ClientException("couldn't read from client", cause = iOException)
If not within native python what would be best practice to achieve what I want to do?
Please note that I want to preserve both stacktraces of the inner and the outer exception, i.e. the following solution is not satisfying:
import sys
def function():
try:
raise ValueError("inner cause")
except Exception:
_, ex, traceback = sys.exc_info()
message = "outer explanation (see nested exception for details)"
raise RuntimeError, message, traceback
if __name__ == "__main__":
function()
produces only the following output:
Traceback (most recent call last):
File "a.py", line 13, in <module>
function()
File "a.py", line 6, in function
raise ValueError("inner cause")
RuntimeError: outer explanation (see nested exception for details)
I cannot see where the RuntimeError occured, so in my understanding the outer stacktrace is lost.
In Python 3, you can use the from keyword to specify an inner exception:
raise ClientException(...) from ioException
You get a traceback that looks like this:
Traceback (most recent call last):
...
IOException: timeout
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
ClientException: couldn't read from client

How can I get better error information with try/except in Python

Consider this try/except block I use for checking error message stored in e.
Try/Catch to get the e
queryString = "SELECT * FROM benchmark WHERE NOC = 2"
try:
res = db.query(queryString)
except SQLiteError, e:
# `e` has the error info
print `e`
The e object here contains nothing more than the above string. When python reports an unhandled error, however, it shows a pretty detailed info as below:
Traceback (most recent call last):
File "fool.py", line 1, in
open("abc.zyz", "r")
IOError: [Errno 2] No such file or directory: 'abc.zyz'
My question is, how can I get the information such as above (the file and the line number etc.)? Or, if e contains this info, how is it stored inside it?
This will show the trace to the error.
import traceback
try:
res = db.query(queryString)
except SQLiteError, e:
# `e` has the error info
print `e`
for tb in traceback.format_tb(sys.exc_info()[2]):
print tb
Like the first 2 answers, use traceback. Here is a more complete example.
import traceback
def foo():
raise RuntimeError('we have a problem')
try:
foo()
except:
traceback.print_exc()
When you run it, you'll see
Traceback (most recent call last):
File "C:\0\tmp\x.py", line 6, in <module>
foo()
File "C:\0\tmp\x.py", line 3, in foo
raise RuntimeError('we have a problem')
RuntimeError: we have a problem
See the traceback library.
If you want to just pass errors up the chain instead of modifying them, you can just use raise within an except block, which will then act like the except block isn't there (aside from any conditional logic/side effects you may have done before the raise).

Categories