Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to suppress displaying the parent exception (the cause) for subsequent exceptions

I'm aware of raise ... from None and have read How can I more easily suppress previous exceptions when I raise my own exception in response?.

However, how can I achieve that same effect (of suppressing the "During handling of the above exception, another exception occurred" message) without having control over the code that is executed from the except clause? I thought that sys.exc_clear() could be used for this, but that function doesn't exist in Python 3.

Why am I asking this? I have some simple caching code that looks like (simplified):

try:
    value = cache_dict[key]
except KeyError:
    value = some_api.get_the_value_via_web_service_call(key)
    cache_dict[key] = value

When there's an exception in the API call, the output will be something like this:

Traceback (most recent call last):
  File ..., line ..., in ...
KeyError: '...'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ..., line ..., in ...
some_api.TheInterestingException: ...

This is misleading, as the original KeyError is not really an error at all. I could of course avoid the situation by changing the try/except (EAFP) into a test for the key's presence (LBYL) but that's not very Pythonic and less thread-friendly (not that the above is thread-safe as is, but that's beside the point).

It's unreasonable to expect all code in some_api to change their raise X to raise X from None (and it wouldn't even make sense in all cases). Is there a clean solution to avoid the unwanted exception chain in the error message?

(By the way, bonus question: the cache thing I used in the example is basically equivalent to cache_dict.setdefault(key, some_api.get_the_value_via_web_service_call(key)), if only the second argument to setdefault could be a callable that would only be called when the value needs to be set. Isn't there a better / canonical way to do it?)

like image 244
user1224797 Avatar asked May 14 '15 10:05

user1224797


People also ask

How do you suppress an exception in Python?

Using Try Exceptexcept ... block to catch the ZeroDivisionError exception and ignore it. In the above code, we catch the ZeroDivisionError exception and use pass to ignore it. So, when this exception happens, nothing will be thrown and the program will just keep running by ignoring the zero number.

Does raising an exception stop the program?

When an exception is raised, no further statements in the current block of code are executed. Unless the exception is handled (described below), the interpreter will return directly to the interactive read-eval-print loop, or terminate entirely if Python was started with a file argument.

What is BaseException in Python?

The BaseException is the base class of all other exceptions. User defined classes cannot be directly derived from this class, to derive user defied class, we need to use Exception class. The Python Exception Hierarchy is like below. BaseException.

How does Python handle unknown exceptions?

When an exception occurs, the Python interpreter stops the current process. It is handled by passing through the calling process. If not, the program will crash. For instance, a Python program has a function X that calls function Y, which in turn calls function Z.


1 Answers

You have a few options here.

First, a cleaner version of what orlp suggested:

try:
    value = cache_dict[key]
except KeyError:
    try:
        value = some_api.get_the_value(key)
    except Exception as e:
        raise e from None
    cache_dict[key] = value

For the second option, I'm assuming there's a return value hiding in there somewhere that you're not showing:

try:
    return cache_dict[key]
except KeyError:
    pass
value = cache_dict[key] = some_api.get_the_value(key)
return value

Third option, LBYL:

if key not in cache_dict:
    cache_dict[key] = some_api.get_the_value(key)
return cache_dict[key]

For the bonus question, define your own dict subclass that defines __missing__:

class MyCacheDict(dict):

    def __missing__(self, key):
        value = self[key] = some_api.get_the_value(key)
        return value

Hope this helps!

like image 110
Zachary Ware Avatar answered Oct 14 '22 05:10

Zachary Ware