-2

What is the correct way in Python of catching an exception, and raising another exception? I.e, I want to catch an exception of type 1, manipulate the Exception object and unconditionally raise a second exception of type 2, so that the calling function doesn't see the Exception of type 1 but absolutely sees the exception of type 2 whose construction depends on data accessed from the type-1 Exception.

Here is the code I've tried which doesn't work.

            def d(wrt):
                try:
                    return rt.derivative(wrt).canonicalize()
                except CannotComputeDerivative as e:
                    msg = "\n".join([f"When generating derivatives from {self}",
                                     f"  when computing edges of {rt}",
                                     f"  which canonicalizes to {self.canonicalize()}",
                                     f"  computing derivative of {e.rte}",
                                     f"  wrt={e.wrt}",
                                     f"  derivatives() reported: {e.msg}"])
                    raise CannotComputeDerivatives(msg=msg,
                                                   rte=rt,
                                                   wrt=wrt,
                                                   first_types=fts,
                                                   mdtd=wrts)

The reason I think it doesn't work is because I get the following message as output:

Error
Traceback (most recent call last):
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 95, in d
    return rt.derivative(wrt).canonicalize()
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_singleton.py", line 68, in derivative
    return super().derivative(wrt)
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 72, in derivative
    return self.derivative_down(wrt)
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_singleton.py", line 91, in derivative_down
    raise CannotComputeDerivative(
rte.r_rte.CannotComputeDerivative

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/jnewton/Repos/python-rte/pyrte/tests/rte_tests.py", line 615, in test_derivatives
    self.assertTrue(rt.derivatives())
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 111, in derivatives
    return trace_graph(self, edges)
  File "/Users/jnewton/Repos/python-rte/pyrte/genus/utils.py", line 199, in trace_graph
    es = edges(v0)  # List[(L,V)]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in edges
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in <listcomp>
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 103, in d
    raise CannotComputeDerivatives(msg=msg,
rte.r_rte.CannotComputeDerivatives

Following the suggestion of MisterMiyagi I added from None after the raise CannotComputeDerivatives(...). This makes progress, but it seems the unittest environment doesn't really like it. The displayed message during unit testing is shown below. It looks like unittest sadly truncates the message.

Error
Traceback (most recent call last):
  File "/Users/jnewton/Repos/python-rte/pyrte/tests/rte_tests.py", line 615, in test_derivatives
    rt.derivatives()
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 111, in derivatives
    return trace_graph(self, edges)
  File "/Users/jnewton/Repos/python-rte/pyrte/genus/utils.py", line 199, in trace_graph
    es = edges(v0)  # List[(L,V)]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in edges
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in <listcomp>
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 103, in d
    raise CannotComputeDerivatives(msg=msg,
rte.r_rte.CannotComputeDerivatives

============= To follow up on the solution: The original problem was that the __init__ function for the CannotComputeDerivatives class was calling super().__init__() without passing the message string. I've updated the class definition as follows.

class CannotComputeDerivatives(Exception):
    def __init__(self, msg, rte, wrt, first_types, mdtd):
        self.msg = msg
        self.rte = rte
        self.wrt = wrt
        self.first_types = first_types
        self.mdtd = mdtd
        super().__init__(msg)

The result is that I get beautiful error messages during unit testing:

Error
Traceback (most recent call last):
  File "/Users/jnewton/Repos/python-rte/pyrte/tests/rte_tests.py", line 615, in test_derivatives
    self.assertTrue(rt.derivatives())
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 111, in derivatives
    return trace_graph(self, edges)
  File "/Users/jnewton/Repos/python-rte/pyrte/genus/utils.py", line 199, in trace_graph
    es = edges(v0)  # List[(L,V)]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in edges
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 109, in <listcomp>
    return [(td, d(td)) for td in wrts]
  File "/Users/jnewton/Repos/python-rte/pyrte/rte/r_rte.py", line 103, in d
    raise CannotComputeDerivatives(msg=msg,
rte.r_rte.CannotComputeDerivatives: When generating derivatives from Singleton(SOr(odd?, SAtomic(Test2)))
  when computing edges of Singleton(SOr(odd?, SAtomic(Test2)))
  which canonicalizes to Singleton(SOr(odd?, SAtomic(Test2)))
  computing derivative of Singleton(SOr(odd?, SAtomic(Test2)))
  wrt=SOr(SAtomic(Test2), odd?)
  derivatives() reported: Singleton.derivative_down cannot compute derivative of Singleton(SOr(odd?, SAtomic(Test2)))
  wrt=SOr(SAtomic(Test2), odd?)
  disjoint=False
  subtypep=None
Jim Newton
  • 594
  • 3
  • 16
  • This looks like a very accurate description of what your code does. If you don't want the caller to know about the original exception, don't raise the new one inside the exception handler. – Scott Hunter Jul 06 '21 at 13:09
  • 4
    Can you clarify what you are asking? Chaining exceptions is done on purpose by Python to not hide error details. You can ``raise ... from None`` to suppress a previous exception. – MisterMiyagi Jul 06 '21 at 13:14
  • @ScottHunter, I don't want the caller to know about the original exception, however I do want him to know about the second one. – Jim Newton Jul 06 '21 at 13:40
  • Then "...don't raise the new one inside the exception handler", or (even better) do what @MisterMiyagi said. – Scott Hunter Jul 06 '21 at 13:45
  • @ScottHunter, yes trying what MisterMiyagi said. But the advise about not raising the original exception is not really practical. The first exception provides crucial information which is needed to compute the second exception, and is generated in a fairly deep recursive descent of an expression tree, which might be large. – Jim Newton Jul 06 '21 at 13:48
  • Again, can you please clarify what behaviour you expect? A [mcve] might be useful. – MisterMiyagi Jul 06 '21 at 13:49
  • @MisterMiyagi, I think your original suggestion is the correct solution. My other comments were just to explain to posterity why the suggestion of ScottHunter wasn't applicable advise in my special case. You might want to add that as the answer to the question, and I'll mark it as answered. There is a new question, which I'll handle elsewhere having to do with how to make `unittest` display the exception message. – Jim Newton Jul 06 '21 at 13:51
  • BTW, knowing about `raise ... from` leads us to a different thread about throwing exceptions within exception handlers. https://stackoverflow.com/questions/24752395/python-raise-from-usage – Jim Newton Jul 06 '21 at 13:53
  • You *explicitly* asked "...so that the calling function doesn't see the Exception of type 1". – Scott Hunter Jul 06 '21 at 13:55
  • @ScottHunter, OK, thanks for the clarification. I have updated the orignal post to clarify that point. I hope it is clearer now. – Jim Newton Jul 06 '21 at 14:05

1 Answers1

0

Thanks @MisterMiyagi for the suggestion to add from None after the raise .... This averts the problem that the system complains about exception within exception.

Jim Newton
  • 594
  • 3
  • 16