"except" statement with same exception class as parameter twice

Question:

In Python, how can I use except block with same exception name twice in try/except statements without need to wrap code into one more try/except block?

Simple example (here each call of pages.get may raise the exception):

try:
    page = pages.get(lang=lang)
except Page.DoesNotExist:
    if not lang == default_lang:
        page = pages.get(lang=default_lang)
    else:
        raise Page.DoesNotExist
except Page.DoesNotExist:
    page = pages[0]

For now, in my Django app I do handling like this (but I don’t want “extra” try block here):

try:
    try:
        page = pages.get(lang=lang)
    except Page.DoesNotExist:
        if not lang == default_lang:
            page = pages.get(lang=default_lang)
        else:
            raise Page.DoesNotExist
except Page.DoesNotExist:
    page = pages[0]

Any handling code better than above is appreciated! 🙂

Thanks.

Asked By: neoascetic

||

Answers:

You can’t do this either and expect the elif to execute:

if foo == bar:
  # do "if"
elif foo == bar:
  # do "elif"

And there’s no reason to do this, really. Same for your except concern.

Here’s the disassembled Python bytecode of your first code snippet:

 13           0 SETUP_EXCEPT            10 (to 13)

 14           3 LOAD_GLOBAL              0 (NameError)
              6 RAISE_VARARGS            1
              9 POP_BLOCK           
             10 JUMP_FORWARD            44 (to 57)

 15     >>   13 DUP_TOP             
             14 LOAD_GLOBAL              0 (NameError)
             17 COMPARE_OP              10 (exception match)
             20 POP_JUMP_IF_FALSE       35
             23 POP_TOP             
             24 POP_TOP             
             25 POP_TOP             

 16          26 LOAD_GLOBAL              0 (NameError)
             29 RAISE_VARARGS            1
             32 JUMP_FORWARD            22 (to 57)

 17     >>   35 DUP_TOP             
             36 LOAD_GLOBAL              0 (NameError)
             39 COMPARE_OP              10 (exception match)
             42 POP_JUMP_IF_FALSE       56
             45 POP_TOP             
             46 POP_TOP             
             47 POP_TOP             

 18          48 LOAD_CONST               1 (1)
             51 PRINT_ITEM          
             52 PRINT_NEWLINE       
             53 JUMP_FORWARD             1 (to 57)
        >>   56 END_FINALLY         
        >>   57 LOAD_CONST               0 (None)
             60 RETURN_VALUE        

It’s obvious that the first COMPARE_OP to NameError (at offset 17) will catch the exception and jump to after the second such comparison (at offset 36).

Answered By: Eli Bendersky

Exception in Python or any Sane Language never works the way you are intending it to be. Every time an exception is raised it should trace back a stack/scope. Any exception that is raised in the current scope can only be handled once per stack/scope.
Think it this was, every scope has a single exception handling mechanism, with a filter function which simply filters the exception as the order mentioned for a match. If a match happens, it is handled as per the designated Exception Handler. If any exception is re-thrown or generated it should be handled by the next Exception Handling Mechanism which is just immediate to the next scope.

Looking in to the example, I am wondering why would you like to complicate the fact by raising another exception but rather not handling it in the else block

try:
    page = pages.get(lang=lang)
except Page.DoesNotExist:
    if not lang == default_lang:
        page = pages.get(lang=default_lang)
    else:
        page = pages[0]
Answered By: Abhijit

Though, I can’t currently figure out a better way for your use-case, Python’s try/except statement has some extra features:

try:
  pass
  # run your code
except:
  pass
  # run your error handling, etc.. 
else:
  pass
  # is run whenever an exception didn't happen
finally:
  pass
  # will be executed always (good for cleaning up)
Answered By: plaes

Exceptions raised in an except block are not handled by the same try/except block, so your example cannot work. Even if you could, your example would actually give rise to an infinite loop, as the first except NameError would simply catch the exception raised from within itself again, and raise NameError again, and so on forever.

This feature is by design, as it would otherwise be impossible to write an exception handler that inspects an exception but then raises the exception outwards. except blocks only handle exceptions thrown from within the try block. If you want to handle an exception thrown from within an except block, that block needs to be inside a try block.

Answered By: Ben

I suggest you create a function to get pages for you, eg. like this:

def get_page(language):
    if language == default_lang:
        lang_list = [language]
    else:
        lang_list = [language, default_lang]

    for lang in lang_list:
        try:
            return pages.get(lang=lang)
        except Page.DoesNotExist:
            pass

    return pages[0]
Answered By: Janne Karila

Two ways include either using sys.exc_info or an exception’s value attribute if it has one:

from random import random
from sys import exc_info


# using sys.exc_info
try:
    if random() < 0.5:
        raise Exception("foo")
    raise Exception("bar")
except:
    if exc_info()[1] == "foo":
        print("foo")
    print("bar")

# more complicated example using value attribute
try:
    rand = random()
    if rand < 0.25:
        raise ValueError("one-tile")
    elif rand < 0.25:
        raise ValueError("two-tile")
    elif rand < 0.75:
        raise TypeError("three-tile")
    raise TypeError("four-tile")
except ValueError as e:
    if e.value in ("one-tile", "two-tile",):
        print("it's a value error")
    else:
        raise # for something new
except TypeError as e:
    if e.value.startswith("three"):
        print("iii")
    elif e.value.startswith("four"):
        print("iv")
    # ...
Answered By: thebadgateway