3

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?

LondonRob
  • 73,083
  • 37
  • 144
  • 201
  • 2
    in your case, you're returning from except block. So it is equivalent. What if you didn't return? that wouldn't be equivalent. – Jean-François Fabre Oct 10 '17 at 15:02
  • 2
    Possible duplicate of [Python try-else](https://stackoverflow.com/questions/855759/python-try-else) – Alexander Trakhimenok Oct 10 '17 at 15:07
  • 1
    I *think* this question is slightly different to the proposed dupes. I'll try and explain how, and see how that goes. (Maybe it's not really different!) – LondonRob Oct 10 '17 at 16:23
  • @Jean-FrançoisFabre, yes I think that's the crucial distinction here, isn't it? Because the except block always returns, the else block is not needed. – LondonRob Oct 10 '17 at 16:30

1 Answers1

1

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.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219