2

I'm trying to catch an exception inside of a function, and handle it outside of the function in the top level functionality. I found a lot of great resources for creating a user defined function, as well as how I can catch them using raise, however it's not working out how I would expect.

I have defined two functions, DontThrow and MyException. Nothing ever catches DontThrow so it should never be handled, but yet the second my code gets to the class delcaration, it does exactly why I titled it not to do! It's like python is reading from the top down doing everything regardless of if it's caught anything

How can I cause my exceptions to only handle if they are raised?

import sys

class DontThrow(Exception):
    print("You shouldn't see this")
    sys.exit(1)

class MyException(Exception):
    print("Bad exception")
    sys.exit(1)

def main():
    x = 5
    try:
        if x < 6:
          raise MyException
    finally:
        pass

Output:
You shouldn't see this

Podo
  • 709
  • 9
  • 30
  • May be this will help you in some way https://stackoverflow.com/questions/1319615/proper-way-to-declare-custom-exceptions-in-modern-python – Arpit Solanki Feb 16 '18 at 06:38
  • @ArpitSolanki I actually say that, and have another example I tried using the same class definition. I didn't spend too much time on it given that it was 8 years old, and the differences between python 2 and 3 seem major from what I can tell. How do I know if an 8 year old post is still valid? – Podo Feb 16 '18 at 06:40
  • I tried those and they are perfectly valid. At least the top ones and reliable ones – Arpit Solanki Feb 16 '18 at 06:42
  • 1
    **"It's like python is reading from the top down"** -- well that's exactly how Python interprets a program. You may also want to read: https://docs.python.org/3/reference/executionmodel.html – Ashwini Chaudhary Feb 16 '18 at 06:52
  • Thanks! I didn't even know it was called execution model, so that's a big help. – Podo Feb 16 '18 at 06:55

2 Answers2

3

Python exceptions are classes, and classes are executed at the time of definition.

For example consider this code:

import sys
class test:
    print("hey")
    sys.exit()

In this test is never instantiated but the print statement will be called. I suspect this is a surprise for you, but it is the cause of the behavior you see.

If you want to have your exceptions quit the program have that behavior in the except block, not in the exception itself.

How this would work with your current exception hierarchy would be something like this:

import sys

class DontThrow(Exception):
    pass

class MyException(Exception):
    pass

def main():
    x = 5
    try:
        if x < 6:
            raise MyException
    except DontThrow:
        print("You shouldn't see this")
        sys.exit(1)
    except MyException:
        print("Bad exception")
        sys.exit(1)
    else:
        print("no exception was thrown")
    finally:
        print("This always gets called")

if __name__ == "__main__":
    main()

Note that an uncaught exception will exit the program anyway, so if something isn't caught there's no need to try to call sys.exit() directly

shuttle87
  • 15,466
  • 11
  • 77
  • 106
  • yeah coming from c++, that's not at all how i'm used to things working. – Podo Feb 16 '18 at 06:37
  • @Podo c++ is very different in this regard, run-time code in C++ is only ever executed after a class is instantiated. (sure the metaprogramming bit is a bit different but that is at compile time, perhaps the default behavior of python is a bit more like c++ metaprogramming in regards to *when* things happen although it's very different in the details of course.) – shuttle87 Feb 16 '18 at 06:41
  • So then is it not possible to catch an exception inside of a function and have it actually handle the exception outside of a function? – Podo Feb 16 '18 at 06:43
  • @Podo, I'll make an edit and you can explain if this is what you mean – shuttle87 Feb 16 '18 at 06:46
  • @Podo [It's possible](https://ideone.com/g61tHf), you just need to move the code in your classes to `__init__` method. But raising another exception from an exception doesn't make much sense, the proper way, like shuttle87 mentioned is to handle it in an `except` block based on the exception raise. – Ashwini Chaudhary Feb 16 '18 at 06:53
  • That works, and it's nice to see how a personal exception would be used and caught in a function like main, but say I had another function call `foo` and `foo` actually caught an exception, but I wanted to handle it outside of `foo`. Like, catch it, then "ooh, gotta error, lets exit this function and go to the place that deals with it" rather than having all excpetions handled in a try block. If I have 20 possible exceptions, it would be very messy. Would be nice to just be able to raise the exception and leave it at that. Does that make sense? – Podo Feb 16 '18 at 06:54
  • @AshwiniChaudhary, read my above comment. Is there only one way to handle that hypothetical situation? – Podo Feb 16 '18 at 06:58
  • @Podo Check the link under *It's possible* phrase. – Ashwini Chaudhary Feb 16 '18 at 06:59
  • Ah, perfect! Are you saying that it's bad practice to do it though? It looks quite nice to me... Especially if there's a lot of possible exceptions. Also, do you have any documentation links for your arguments? They work, but I don't understand them – Podo Feb 16 '18 at 07:01
  • @Podo It makes things **implicit**, shuttle87's suggestion makes it clear that this exception results in system exit by looking at the code itself, my suggestion will require jumping to that exception code to figure out what happened. Plus, say, in future you replace the `sys.exit()` call with another exception, then you won't get the benefits of [exception chaining](https://www.python.org/dev/peps/pep-3134/) introduced in Python 3. – Ashwini Chaudhary Feb 16 '18 at 07:19
  • @Podo *Especially if there's a lot of possible exceptions.* -- you probably want to explain this a bit in your question, from my understanding you could do something like this to handle multiple exceptions together in an `except` block: `except (MyException1, MyException2, MyException3): sys.exit()`. – Ashwini Chaudhary Feb 16 '18 at 07:20
-3

It is depend on the order of class definition.If you change the order like below than the output is like Bad exception

import sys

class MyException(Exception):
    print("Bad exception")
    sys.exit(1)

class DontThrow(Exception):
    print("You shouldn't see this")
    sys.exit(1)

def main():
    x = 5
    try:
        if x < 6:
          raise MyException
    finally:
        pass
Arpit Solanki
  • 9,567
  • 3
  • 41
  • 57
rahul mehra
  • 418
  • 2
  • 13
  • 1
    That fails to fix the problem, rather swapping the two to still have a bad result. – Podo Feb 16 '18 at 06:36