I know what a try-else block is, but consider the following two functions:
# Without else
def number_of_foos1(x):
try:
number = x['foo_count']
except:
return 0
return number
# With else
def number_of_foos2(x):
try:
number = x['foo_count']
except:
return 0
else:
return number
x_with_foo = dict(foo_count=5)
x_without_foo = 3
Unlike this try-else question we're not adding extra lines to the try block. In both cases the try block is a single line, and the principle of keeping error handling "close" to the errors that caused it is not violated.
The difference is in where we go after the successful try block.
In the first block, the code continues after the except block, and in the second, the code continues at the else.
They obviously give the same output:
In [138]: number_of_foos1(x_with_foo)
Out[139]: 5
In [140]: number_of_foos1(x_without_foo)
Out[140]: 0
In [141]: number_of_foos2(x_with_foo)
Out[141]: 5
In [142]: number_of_foos2(x_without_foo)
Out[142]: 0
Is either preferred? Are they even any different as far as the interpreter is concerned? Should you always have an else when continuing after a successful try or is it OK just to carry on unindented, as in number_of_foos1?
I would say that the case where you enter in the exception block must be rare (that's what we call that an exception). So using else gives too much importance to that block, which isn't supposed to happen in normal operation.
So if an exception occurs, handle the error and return, and forget about it.
Using else here add more complexity, and you can confirm this by disassembling both functions:
>>> dis.dis(number_of_foos1)
4 0 SETUP_EXCEPT 14 (to 17)
5 3 LOAD_FAST 0 (x)
6 LOAD_CONST 1 ('foo_count')
9 BINARY_SUBSCR
10 STORE_FAST 1 (number)
13 POP_BLOCK
14 JUMP_FORWARD 12 (to 29)
6 >> 17 POP_TOP
18 POP_TOP
19 POP_TOP
7 20 LOAD_CONST 2 (0)
23 RETURN_VALUE
24 POP_EXCEPT
25 JUMP_FORWARD 1 (to 29)
28 END_FINALLY
8 >> 29 LOAD_FAST 1 (number)
32 RETURN_VALUE
>>> dis.dis(number_of_foos2)
<exactly the same beginning then:>
15 20 LOAD_CONST 2 (0)
23 RETURN_VALUE
24 POP_EXCEPT
25 JUMP_FORWARD 5 (to 33)
28 END_FINALLY
17 >> 29 LOAD_FAST 1 (number)
32 RETURN_VALUE
>> 33 LOAD_CONST 0 (None)
36 RETURN_VALUE
>>>
As you see in the second example, addresses 24, 25, 28, 33 and 36 aren't reachable, that's because Python inserts jumps to the end of the code, and also a default return None in the main branch. All this code is useless, and would vouch for snippet #1 which is simpler and returns the result in the main branch.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With