19

I have hardly ever noticed a python program that uses else in a for loop.

I recently used it to perform an action based on the loop variable condition while exiting; as it is in the scope.

What is the pythonic way to use an else in a for loop? Are there any notable use cases?

And, yea. I dislike using break statement. I'd rather set the looping condition complex. Would I be able to get any benefit out of it, if I don't like to use break statement anyway.

Worth noting that for loop has an else since the language inception, the first ever version.

2
  • Hmm, interesting. Is the for-else statement a Python specific thing or do other languages have it? I've never heard of it before (but I've never used Python, just heard many good things about it)
    – David Ly
    Commented Mar 26, 2009 at 14:35
  • I am trying to think of how to search for uses of this construct in the extant python source code, to see if it exists outside of SO. Commented Mar 26, 2009 at 14:44

8 Answers 8

19

What could be more pythonic than PyPy?

Look at what I discovered starting at line 284 in ctypes_configure/configure.py:

    for i in range(0, info['size'] - csize + 1, info['align']):
        if layout[i:i+csize] == [None] * csize:
            layout_addfield(layout, i, ctype, '_alignment')
            break
    else:
        raise AssertionError("unenforceable alignment %d" % (
            info['align'],))

And here, from line 425 in pypy/annotation/annrpython.py (clicky)

if cell.is_constant():
    return Constant(cell.const)
else:
    for v in known_variables:
        if self.bindings[v] is cell:
            return v
    else:
        raise CannotSimplify

In pypy/annotation/binaryop.py, starting at line 751:

def is_((pbc1, pbc2)):
    thistype = pairtype(SomePBC, SomePBC)
    s = super(thistype, pair(pbc1, pbc2)).is_()
    if not s.is_constant():
        if not pbc1.can_be_None or not pbc2.can_be_None:
            for desc in pbc1.descriptions:
                if desc in pbc2.descriptions:
                    break
            else:
                s.const = False    # no common desc in the two sets
    return s

A non-one-liner in pypy/annotation/classdef.py, starting at line 176:

def add_source_for_attribute(self, attr, source):
    """Adds information about a constant source for an attribute.
    """
    for cdef in self.getmro():
        if attr in cdef.attrs:
            # the Attribute() exists already for this class (or a parent)
            attrdef = cdef.attrs[attr]
            s_prev_value = attrdef.s_value
            attrdef.add_constant_source(self, source)
            # we should reflow from all the reader's position,
            # but as an optimization we try to see if the attribute
            # has really been generalized
            if attrdef.s_value != s_prev_value:
                attrdef.mutated(cdef) # reflow from all read positions
            return
    else:
        # remember the source in self.attr_sources
        sources = self.attr_sources.setdefault(attr, [])
        sources.append(source)
        # register the source in any Attribute found in subclasses,
        # to restore invariant (III)
        # NB. add_constant_source() may discover new subdefs but the
        #     right thing will happen to them because self.attr_sources
        #     was already updated
        if not source.instance_level:
            for subdef in self.getallsubdefs():
                if attr in subdef.attrs:
                    attrdef = subdef.attrs[attr]
                    s_prev_value = attrdef.s_value
                    attrdef.add_constant_source(self, source)
                    if attrdef.s_value != s_prev_value:
                        attrdef.mutated(subdef) # reflow from all read positions

Later in the same file, starting at line 307, an example with an illuminating comment:

def generalize_attr(self, attr, s_value=None):
    # if the attribute exists in a superclass, generalize there,
    # as imposed by invariant (I)
    for clsdef in self.getmro():
        if attr in clsdef.attrs:
            clsdef._generalize_attr(attr, s_value)
            break
    else:
        self._generalize_attr(attr, s_value)
2
  • 1
    The else: clause in classdef.py:176 is unnecessary since they exit the loop via 'return'.. Incidentally, how did you locate these? Just eyeballing the source, or something smarter?
    – John Fouhy
    Commented Mar 26, 2009 at 21:41
  • grep for (for|else) at the same level of indentation within ten lines of each other. If I had the skills of a sea cucumber, I'd figure out where in the PyPy compiler the for ... else flow control happens, add logging, and then build -all. Commented Mar 27, 2009 at 2:03
14

Basically, it simplifies any loop that uses a boolean flag like this:

found = False                # <-- initialize boolean
for divisor in range(2, n):
    if n % divisor == 0:
        found = True         # <-- update boolean
        break  # optional, but continuing would be a waste of time

if found:                    # <-- check boolean
    print(n, "is divisible by", divisor)
else:
    print(n, "is prime")

and allows you to skip the management of the flag:

for divisor in range(2, n):
    if n % divisor == 0:
        print(n, "is divisible by", divisor)
        break
else:
    print(n, "is prime")

Note that there is already a natural place for code to execute when you do find a divisor - right before the break. The only new feature here is a place for code to execute when you tried all divisors and did not find any.

This helps only in conjuction with break. You still need booleans if you can't break (e.g. because you looking for the last match, or have to track several conditions in parallel).

Oh, and BTW, this works for while loops just as well.

any/all

If the only purpose of the loop is a yes-or-no answer, any()/all() functions with a generator or generator expression can be utilized:

if any(n % divisor == 0 
       for divisor in range(2, n)):
    print(n, "is composite")
else:
    print(n, "is prime")

Note the elegancy! The code is 1:1 what you want to say — "if any divisor divides n evenly, from 2 to n, then ...".

[This has similar efficiency to a loop with a break, because the any() function is short-circuiting, only running the generator expression until it yeilds True.]

But that won't give you the actual divisor, as any() always returns exactly True or False. A loop with else: is hard to beat when you need both (A) access to current value that was "found" (B) separate code paths for "found" vs. "not found" cases.

4
  • 1
    Actually, any() can only return True or False, thus divisor = any(d for d in range(2, n) if n % d == 0) won't work (sadly?/!).
    – Semnodime
    Commented Mar 15, 2023 at 8:49
  • 1
    As, any() can only return True or False, there's pythonicly only one elegant and obious way to determine the value which caused the condition to be True, using the pythonic for else.
    – Semnodime
    Commented Mar 15, 2023 at 8:53
  • Ouch 🤦, I'd written that thinking any([x, y, z]) == x or y or z but you're right, it's only True/False. Thanks for the edit! I'm gonna just drop the last non-working divisor = any(..) example; a custom generator returning a wrapper like [d] is workable with pattern matching on its result, but is too off topic to original question. Commented Mar 24, 2023 at 14:22
  • next() builtin can take a 2nd sentinel argument to return if iterator is finished, so something like divisor = next((d for d in range(2, n) if n % d == 0), None) is also workable. Still not idiomatic enough for my taste. Commented Mar 24, 2023 at 14:46
7

If you have a for loop you don't really have any condition statement. So break is your choice if you like to abort and then else can serve perfectly to handle the case where you were not happy.

for fruit in basket:
   if fruit.kind in ['Orange', 'Apple']:
       fruit.eat()
       break
else:
   print 'The basket contains no desirable fruit'
12
  • You misunderstood the concept. The else block is not executed after a break. Commented Mar 26, 2009 at 13:33
  • You are wrong, as else part is executed only when for completes without a break.
    – lprsd
    Commented Mar 26, 2009 at 13:35
  • @Ferdinand: Perhaps his explication is not perfect, but from the code you can clearly see, that it's exactly what he meant.
    – vartec
    Commented Mar 26, 2009 at 13:36
  • @vartec: OK, you are right. To me it read like: Using the else block, you can examine the reason why you did the break. My mistake. Commented Mar 26, 2009 at 13:38
  • Well, if you don't have enought total money in your wallet it will not break, hence it will say you dont have enough money. The else statement is also executed if the wallet is completely empty. Commented Mar 26, 2009 at 13:39
5

Without using break, else blocks have no benefit for for and while statements. The following two examples are equivalent:

for x in range(10):
  pass
else:
  print "else"

for x in range(10):
  pass
print "else"

The only reason for using else with for or while is to do something after the loop if it terminated normally, meaning without an explicit break.

After a lot of thinking, I can finally come up with a case where this might be useful:

def commit_changes(directory):
    for file in directory:
        if file_is_modified(file):
            break
    else:
        # No changes
        return False

    # Something has been changed
    send_directory_to_server()
    return True
6
  • Still, the laster use case, doesnt seem an elegant solution, even for this case.
    – lprsd
    Commented Mar 26, 2009 at 14:08
  • I totally agree and frankly I have never used this construct. Commented Mar 26, 2009 at 14:36
  • 3
    But now, I predict, you are at risk of seeing opportunities to use it everywhere. Commented Mar 26, 2009 at 14:42
  • 2
    I'd probably write: "if file is modified(file): send_directory_to_server(); return True" and drop the else: clause..
    – John Fouhy
    Commented Mar 26, 2009 at 21:38
  • @John: Do you have a better example for the for-else construct? :) Commented Mar 26, 2009 at 22:59
4

Perhaps the best answer comes from the official Python tutorial:

break and continue Statements, and else Clauses on Loops:

Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement

1

I was introduced to a wonderful idiom in which you can use a for/break/else scheme with an iterator to save both time and LOC. The example at hand was searching for the candidate for an incompletely qualified path. If you care to see the original context, please see the original question.

def match(path, actual):
    path = path.strip('/').split('/')
    actual = iter(actual.strip('/').split('/'))
    for pathitem in path:
        for item in actual:
            if pathitem == item:
                break
        else:
            return False
    return True

What makes the use of for/else so great here is the elegance of avoiding juggling a confusing boolean around. Without else, but hoping to achieve the same amount of short-circuiting, it might be written like so:

def match(path, actual):
    path = path.strip('/').split('/')
    actual = iter(actual.strip('/').split('/'))
    failed = True
    for pathitem in path:
        failed = True
        for item in actual:
            if pathitem == item:
                failed = False
                break
        if failed:
            break
    return not failed

I think the use of else makes it more elegant and more obvious.

0

A use case of the else clause of loops is breaking out of nested loops:

while True:
    for item in iterable:
        if condition:
            break
        suite
    else:
        continue
    break

It avoids repeating conditions:

while not condition:
    for item in iterable:
        if condition:
            break
        suite
-1

Here you go:

a = ('y','a','y')
for x in a:
  print x,
else:
  print '!'

It's for the caboose.

edit:

# What happens if we add the ! to a list?

def side_effect(your_list):
  your_list.extend('!')
  for x in your_list:
    print x,

claimant = ['A',' ','g','u','r','u']
side_effect(claimant)
print claimant[-1]

# oh no, claimant now ends with a '!'

edit:

a = (("this","is"),("a","contrived","example"),("of","the","caboose","idiom"))
for b in a:
  for c in b:
    print c,
    if "is" == c:
      break
  else:
    print
4
  • Not so happy! What prevents you from putting '!' within the list, or using the print '!' without the else?
    – lprsd
    Commented Mar 26, 2009 at 14:05
  • If you use the 'print !' without the else, then you get the '!' even if the for loop had a break, and it is impolite to exult in the presence of a break. Commented Mar 26, 2009 at 14:12
  • Also, since tuples are immutable, adding a '!' to the end of a tuple would require copying the whole thing. Commented Mar 26, 2009 at 14:18
  • Ferdinand's example is better than mine because it is more flow-control-y. Commented Mar 26, 2009 at 14:19

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.