"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.
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).
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]
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)
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.
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]
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")
# ...
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.
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).
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]
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)
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.
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]
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")
# ...