Else Clauses on Loop Statements =============================== Python's loop statements have a feature that some people love (Hi!), some people hate, many have never encountered and many just find confusing: an ``else`` clause. This article endeavours to explain some of the reasons behind the frequent confusion, and explore some other ways of thinking about the problem that give a better idea of what is *really* going on with these clauses. Reasons for Confusion --------------------- The major reason many developers find the behaviour of these clauses potentially confusing is shown in the following example:: >>> if [1]: ... print("Then") ... else: ... print("Else") ... Then >>> for x in [1]: ... print("Then") ... else: ... print("Else") ... Then Else The ``if `` header looks very similar to the ``for in `` header, so it's quite natural for people to assume they're related and expect the ``else`` clause to be skipped in both cases. As the example shows, this assumption is incorrect: in the second case, the else clauses triggers even though the iterable isn't empty. If we then look at a common ``while`` loop pattern instead, it just deepens the confusion because *it* seems to line up with the way we would expect the conditional to work:: >>> x = [1] >>> while x: ... print("Then") ... x.pop() ... else: ... print("Else") ... Then Else >>> if x: ... print("Then") ... else: ... print("Else") ... Else Here, the loop runs until the iterable is empty, and then the ``else`` clause is executed, just as it is in the ``if`` statement. A different kind of ``else`` ---------------------------- So what's going on? The truth is that the superficial similarity between ``if `` and ``for in `` is rather deceptive. If we call the ``else`` clause on an ``if`` statement a "conditional else", then we can look to ``try`` statements for a different *kind* of ``else`` clause, a "completion clause":: >>> try: ... pass ... except: ... print("Then") # The try block threw an exception ... else: ... print("Else") # The try block didn't throw an exception ... Else With a completion clause, the question being asked has to do with how an earlier suite of code *finished*, rather than checking the boolean value of an expression. Reaching the ``else`` clause in a ``try`` statement means that the try block actually completed successfully - it didn't throw an exception or otherwise terminate before reaching the end of the suite. This is actually a much better model for what's going on in our ``for`` loop, since the condition the ``else`` is checking for is whether or not the loop was explicitly terminated by a ``break`` statement. While it's not legal syntax, it may be helpful to mentally insert an ``except break: pass`` whenever you encounter a loop with an associated ``else`` clause in order to help remember what it means:: for x in iterable: ... except break: pass # Implied by Python's loop semantics else: ... # No break statement was encountered while condition: ... except break: pass # Implied by Python's loop semantics else: ... # No break statement was encountered What possible use is the current behaviour? ------------------------------------------- The main use case for this behaviour is to implement search loops, where you're performing a search for an item that meets a particular condition, and need to perform additional processing or raise an informative error if no acceptable value is found:: for x in data: if acceptable(x): break else: raise ValueError("No acceptable value in {!r:100}".format(data)) ... # Continue calculations with x But how do I check if my loop never ran at all? ----------------------------------------------- The easiest way to check if a ``for`` loop never executed is to use ``None`` as a sentinel value:: x = None for x in data: ... # process x if x is None: raise ValueError("Empty data iterable: {!r:100}".format(data)) If ``None`` is a legitimate data value, then a custom sentinel object can be used instead:: x = _empty = object() for x in data: ... # process x if x is _empty: raise ValueError("Empty data iterable: {!r:100}".format(data)) For ``while`` loops, the appropriate solution will depend on the details of the loop. But couldn't Python be different? --------------------------------- Backwards compatibility constraints and the general desire not to change the language core without a compelling justification mean that the answer to this question is likely always going to be "No". The simplest approach for any new language to take to avoid the confusion encountered in relation to this feature of Python would be to just leave it out altogether. Many (most?) other languages don't offer it, and there are certainly other ways to handle the search loop use case, including a sentinel based approach similar to that used to detect whether or not a loop ran at all:: result = _not_found = object() for x in data: if acceptable(x): result = x break if result is _not_found: raise ValueError("No acceptable value in {!r:100}".format(data)) ... # Continue calculations with result Closing note: Not so different after all? ----------------------------------------- Attentive readers may have noticed that the behaviour of ``while`` loops still makes sense regardless of whether you think of their ``else`` clause as a conditional else or as a completion clause. We can think of a ``while`` statement in terms of an infinite loop containing a ``break`` statement:: while True: if condition: pass # Implied by Python's loop semantics else: ... # While loop else clause runs here break ... # While loop body runs here If you dig deep enough, it's also possible to relate the completion clause constructs in ``try`` statements and ``for`` loops back to the basic conditional else construct. The thing to remember though, is that it is only ``while`` loops and ``if`` statements that are checking the boolean value of an expression, while ``for`` loops and ``try`` statements are checking whether or not a section of code was aborted before completing normally. However, digging to that deeper level doesn't really provide much more enlightenment when it comes to understanding how the two different forms of ``else`` clause work in practice.