Search code examples
pythonhigher-order-functions

Catching a specific exception in a chain when reraising it is not an option


Consider the following Python code:

def say(words):
    for word in words:
        if word == "Ni":
            raise ValueError("We are no longer the knights who say Ni!")
        print(word)

def blackbox(function, sentence):
    words = sentence.split()
    try:
        function(words)
    except Exception as e:
        raise RuntimeError("Generic Error")

blackbox(say, "Foo Ni Bar")

It prints the following traceback:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [35], in blackbox(function, sentence)
      9 try:
---> 10     function(words)
     11 except Exception as e:

Input In [35], in say(words)
      3 if word == "Ni":
----> 4     raise ValueError("We are no longer the knights who say Ni!")
      5 print(word)

ValueError: We are no longer the knights who say Ni!

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [35], in <cell line: 14>()
     11     except Exception as e:
     12         raise RuntimeError("Generic Error")
---> 14 blackbox(say, "Foo Ni Bar")

Input In [35], in blackbox(function, sentence)
     10     function(words)
     11 except Exception as e:
---> 12     raise RuntimeError("Generic Error")

RuntimeError: Generic Error

Assume I am only interested in the first error. I could simply reraise it by replacing raise RuntimeError("Generic Error") by raise e in blackbox(): end of the story.

Except (!) that I cannot modify the code of blackbox(), which belongs to an external library.

How can I obtain the same result without touching it? My guess is that I could wrap the call to blackbox() in a try... except..., retrieve the chain of exceptions, and select the one I am interested in. But I failed to find anywhere how to do such a thing.

Edit: changed the name and the signature of the second function to make the constraints clearer.


Solution

  • Answering my own question. It is enough to catch the final exception and raise its context, i.e., replace the last line by:

    try:
        blackbox(say, "Foo Ni Bar")
    except Exception as e:
        raise e.__context__
    

    Traceback:

    ---------------------------------------------------------------------------
    RuntimeError                              Traceback (most recent call last)
    Input In [105], in <cell line: 14>()
         14 try:
    ---> 15     blackbox(say, "Foo Ni Bar")
         16 except Exception as e:
    
    Input In [105], in blackbox(function, sentence)
         11 except Exception as e:
    ---> 12     raise RuntimeError("Generic Error")
    
    RuntimeError: Generic Error
    
    During handling of the above exception, another exception occurred:
    
    ValueError                                Traceback (most recent call last)
    Input In [105], in <cell line: 14>()
         15     blackbox(say, "Foo Ni Bar")
         16 except Exception as e:
    ---> 17     raise e.__context__
    
    Input In [105], in blackbox(function, sentence)
          8 words = sentence.split()
          9 try:
    ---> 10     function(words)
         11 except Exception as e:
         12     raise RuntimeError("Generic Error")
    
    Input In [105], in say(words)
          2 for word in words:
          3     if word == "Ni":
    ----> 4         raise ValueError("We are no longer the knights who say Ni!")
          5     print(word)
    
    ValueError: We are no longer the knights who say Ni!