Dunder method __throw__ for Exceptions object behavior

Hi everyone,
I would have had an idea. In a “very” specific case, I had to throw a raise Exception and before panicking, create a file.

This behavior is obviously not expected by the default BaseException object.
In my case I handled it by creating an ad-hoc function that executed code before creating and throwing the exception.

I thought it was possible to create a dunder method throw where you can insert additional behavior that the raise keyword performs (or evaluates) before throwing the exception and exiting.

A small example:

class MyException(Exception):

    def __throw__(self):
        print('This printed before throws')

raise MyException('test')

and the exception is raising:

This printed before throws
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    raise MyException('test')
 ~^^^^^^^^^^^^^^^^^^^^^^^^~
MyException: test

However, if there is already a way, I ask for forgiveness and help to create this thing in a more…pythonic way.

Thanks for everything in advance

1 Like

You could do what you want in the exception’s __new__ or __init__ method, which is usually but not always executed right before the exception is thrown. Your solution of creating a function that performs the side effect and then raises the exception also works. Personally I might prefer that, as it is a bit of a code smell to have side effects in the constructor.

2 Likes

Given …

>>> try:
...     1/0
... finally:
...     print("will be printed just before the exception is raised")
...
will be printed just before the exception is raised
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

… how about calling it __finally__?

The function is an idea that came to mind to speed up the solution. It’s not very elegant or pythonic.

I haven’t figured out how to use the __new__ constructor to make the raise keyword call __throw__ (or __finally__, I like the name!).

Maybe you meant something like that?

class MyException(Exception):
    def __new__(cls, name):

        def throw():
            print('This printed before throws')
            
        obj = Exception.__new__(cls)
        obj.__throw__ = throw
        return obj

myex = MyException('test')

It works if you call the method manually…but my idea was to provide a call directly from raise…
Or better; raise keyword calls the dunder method __throw__ (or __finally__) by default so that it always executes something before throwing the exception, and if the method is not defined, it just throws the exception…

# Does works
raise myex.__throw__()
# Doesn't works
raise myex

I don’t think this is a common enough need to warrant changes to the standard Exception class. The workarounds I am suggesting are ways to do what you want in your own code, without requiring language changes.

3 Likes

I understand. It seems reasonable to me. It is actually an uncommon requirement.

I will consider this idea. Thanks for now.

Options include:

  • Just manually include the necessary code before raising the exception:
print("Need to do this before raising")
raise MyException('test')
  • If it needs to happen whenever an exception reaches a particular part of the code, have that code catch the exception, do the work and re-raise:
try:
    do_something_dangerous()
except MyException as e:
    print("Doing something before the stack trace shows up")
    raise e

If it needs to happen whether or not the exception is raised, use finally:

try:
    do_something_dangerous()
finally:
    print("this happens even if MyException doesn't occur")
    print("if the exception does occur, this happens first")
  • If it needs to happen whenever the exception is raised, use a function to wrap the process of raising it:
def do_something_then_panic():
    print("Doing something before raising a serious exception")
    raise MyException('test')

That won’t work, and is not the suggestion.

The idea is that in normal code, the exception object would only be created immediately before raising it. So if you always need to e.g. write to a file before raising that specific exception, do it as part of the process of creating the exception. Thus, the last idea is:

  • Do it in the MyException constructor:
class MyException:
    def __init__(self, arg):
        print("This happens when creating the exception, therefore, before it is raised")

However, this is a code smell, and it would mess up code that creates exceptions to throw later (although that’s probably itself a code smell).

1 Like

Thanks so much for your suggest, @kknechtel.
In fact, your latest proposal is the solution I was looking for. In fact, since the constructor is not defined in BaseException I can modify the behavior of what happens before when the object itself is constructed.

Thank you all for your support and sorry for the impertinence of my proposal.
You are a great community!

1 Like

I feel sure I’ve encountered this use case somewhere. I can’t remember where, but I do remember what I did about it.

@kknechtel’s excellent list of suggestions doesn’t include the solution I chose, though, so here you go, in case it fits your needs:

  • Make a function that runs your extra code and then returns the (either created or passed in) exception.
class MyException(Exception):
    pass # just a normal exception subclass

def do_extra_things_a() -> Exception:
    ex = MyException()
    do_something()
    return ex
# --or--
def do_extra_things_b(ex: MyException) -> Exception:
    do_something()
    return ex

def use_the_exception():
    if some_operation_failed():
        raise do_extra_things_a()
        # --or--
        raise do_extra_things_b(MyException())

My reasoning:

  • Making do_extra_things_* raise the exception itself doesn’t make it clear at the call site that an exception will be raised.
  • Making the exception’s constructor do the extra things doesn’t make it clear at the raise statement that the extra things will happen.
  • Doing the extra things on the line before the raise statement doesn’t make it clear that those extra things are specifically to do with raising the exception, so if someone needs to do the same thing later, they might not remember to include both steps (the extra things, and the raise), or those two steps might get split up during refactoring.

So, by returning the exception instance from the function, it’s made as clear as possible that the two steps belong together and shouldn’t be separated.