Python's with/for...else and try...else
Tags: python, tutorial
Published: 2020-08-09 13:32:38

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:

```python
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}")
```

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
```

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
```

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](https://docs.python.org/3/reference/compound_stmts.html#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`](https://docs.python.org/3/reference/simple_stmts.html#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:

```python
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}")
```

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
```

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

```
i is now 0
```

## 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:

```python
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}!")
```

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](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement) states:

> The optional `else` clause is executed if the control flow leaves the [`try`](https://docs.python.org/3/reference/compound_stmts.html#try) suite, no exception was raised, and no [`return`](https://docs.python.org/3/reference/simple_stmts.html#return), [`continue`](https://docs.python.org/3/reference/simple_stmts.html#continue), or [`break`](https://docs.python.org/3/reference/simple_stmts.html#break) statement was executed. Exceptions in the else clause are not handled by the preceding [`except`](https://docs.python.org/3/reference/compound_stmts.html#except) clauses.

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

```python
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!")
```

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

```
Got exception
Finally!
```

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

```
Else!
Finally!
```

&nbsp;

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:

```python
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!")
```

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](https://stackoverflow.com/a/16138864/256815) 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:

```python
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!")
```

The output for this is:

```
Try body
Got exception
Finally!
```

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:

```python
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!")
```

Which gives the following output:

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