DEV Community

Kah
Kah

Posted on

Python's with/for...else and try...else

In most programming languages like C, Java and Javscript, the else clause is used with if statements. But did you know in Python the while and for loops as well as the try statement can also have else clauses?

In the while loop

To get a sense of how the else works with the loops, it's easy to write some code to play with. Here's the first one I tried:

basket = ["apple", "orange", "pear", "banana"]
i = 0
while basket[i] != "apple":
  print(f"Its not {i}")
  i = i + 1
else:
  print("Else block ran!")

print(f"i is now {i}")
Enter fullscreen mode Exit fullscreen mode

What do you think will be the output? At first, I thought it would output Else block ran! followed by i is now 0 (i.e. it would execute the else block) because the first item in the list is already apple. The output certainly shows this happens:

Else block ran!
i is now 0
Enter fullscreen mode Exit fullscreen mode

But, what if we changed the condition in the while loop to basket[i] != "banana"? The body of the while loop should now run, so I first thought it would surely skip the else clause ...

Its not 0
Its not 1
Its not 2
Else block ran!
i is now 3
Enter fullscreen mode Exit fullscreen mode

Nope! It printed Else block ran!, so the else block must've ran!

So what's going on here? Turning to the documentation on the while statement:

This repeatedly tests the expression and, if it is true, executes the first suite; if the expression is false (which may be the first time it is tested) the suite of the else clause, if present, is executed and the loop terminates.

A break statement executed in the first suite terminates the loop without executing the else clause’s suite.

So, if we want to skip the else block, loop needs to exit with a break.

Rewriting the while loop to use break to exit when it finds a match:

basket = ["apple", "orange", "pear", "banana"]
i = 0
while i < len(basket):
  if basket[i] == "banana":
    # We found what we were looking for.
    break
  print(f"Its not {i}")
  i = i + 1
else:
  # We couldn't find it in the list.
  print("Else block ran!")

print(f"i is now {i}")
Enter fullscreen mode Exit fullscreen mode

The output now doesn't contain the print in the else block:

Its not 0
Its not 1
Its not 2
i is now 3
Enter fullscreen mode Exit fullscreen mode

And just to check, setting the if condition back to basket[i] == "apple" also doesn't run the else block:

i is now 0
Enter fullscreen mode Exit fullscreen mode

In the for loop

The behaviour of the else clause in the for loop is the same as that of the while loop. Rewriting the while loop to use for instead:

basket = ["apple", "orange", "pear", "banana"]
find = "banana"
for f in basket:
  if f == find:
    print(f"We found {find}!")
    break;
else:
  print(f"There isn't any {find}!")
Enter fullscreen mode Exit fullscreen mode

Running this, you'll get the output We found banana!, but changing find to "grape" will result in There isn't any grape!.

In try statements

For the else in try statements, the documentation states:

The optional else clause is executed if the control flow leaves the try suite, no exception was raised, and no return, continue, or break statement was executed. Exceptions in the else clause are not handled by the preceding except clauses.

Here's an example of a try with an else clause:

try:
  # This will raise an AssertionError
  assert 1 == 2
except:
  # Catch all exceptions
  print("Got exception")
else:
  print("Else!")
finally:
  # Finally always runs at the end
  print("Finally!")
Enter fullscreen mode Exit fullscreen mode

With the AssertionError thrown, it skips the else. This is the output:

Got exception
Finally!
Enter fullscreen mode Exit fullscreen mode

But changing the assertion to pass (e.g. assertion 1 == 1), no exception is thrown and, thus, else is executed:

Else!
Finally!
Enter fullscreen mode Exit fullscreen mode

 

However, you could do the same thing without an else clause - anything within the try block after the line that can throw the exception is effectively the "else". For example:

try:
  # If this assertion fails, this will throw an AssertionError
  assert 1 == 1

  # No exception thrown, so everything here is the "else"
  print("Else!")
except:
  # Catch all exceptions
  print("Got exception")
finally:
  # Finally always runs at the end
  print("Finally!")
Enter fullscreen mode Exit fullscreen mode

So given this alternative, apart from style, why would you use the else clause? The difference is in when the else block throws an exception (thanks to this Stackoverflow answer for pointing it out). If it's part of the try block, the exception may be handled by one of the try's except handlers, but it's passed up the chain if it's in an else clause - none of the handlers defined in the same try statement will handle exceptions from the else.

To see this, let's modify the try statement so that the assert becomes the else. First, without the separate else clause:

try:
  # Body of try. Won't throw an exception. 
  print("Try body")

  # The "else" part. Make it throw an exception.
  assert 1 == 2
except:
  # Catch all exceptions
  print("Got exception")
finally:
  # Finally always runs at the end
  print("Finally!")
Enter fullscreen mode Exit fullscreen mode

The output for this is:

Try body
Got exception
Finally!
Enter fullscreen mode Exit fullscreen mode

Notice the except block handled the exception from our "else". So to check that using the else clause doesn't trigger one of the handlers:

try:
  # Body of try
  print("Try body")
except:
  # Catch all exceptions
  print("Got exception")
else:
  # Make else thrown an exception
  assert 1 == 2
finally:
  # Finally always runs at the end
  print("Finally!")
Enter fullscreen mode Exit fullscreen mode

Which gives the following output:

Try body
Finally!
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    assert 1 == 2
AssertionError
Enter fullscreen mode Exit fullscreen mode

Top comments (0)