When you have an outer and an inner loop, how do you continue the outer loop from a condition inside the inner loop? Consider the following code:
for i in range(10):
for j in range(9):
if i <= j:
# break out of inner loop
# continue outer loop
print(i,j)
# don't print unless inner loop completes,
# e.g. outer loop is not continued
print("inner complete!")
Here, we want to print for all i
∈ [0,10)
all numbers j
∈ [0,9)
that are less than or equal to i and we want to print complete once we’ve found an entire list of j
that meets the criteria. While this seems like a fairly contrived example, I’ve actually encountered this exact situation in several places in code this week, and I’ll provide a real example in a bit.
My first instinct simply uses a function to use return
to do a “hard break” out of the loop. This allows us to short-circuit functionality by exiting the function, but doesn’t actually provide continue
functionality, which is the goal in the above example. The technique does work, however, and in multi-loop situations is probably the best bet.
def inner(i):
for j in range(9):
if i <= j:
# Note if this was break, the print statement would execute
return
print(i,j)
print("inner complete")
for i in range(10):
inner(i)
Much neater, however is using for/else
. The else
block fires iff the for loop it is connected with completes. This was very weird to me at first, I thought else
should trigger if break
. Think of it this way though:
You’re searching through a list of things,
for item in collection
and you plan tobreak
when you’ve found the item you’re looking for,else
you do something if you exhaust the collection and didn’t find what you were looking for.
Therefore we can code our loop as follows:
for i in range(10):
for j in range(9):
if i <= j:
break
print(i,j)
else:
# Outer loop is continued
continue
print("inner complete!")
This is a little strange, because it is probably more appropriate to put our print
in the else
block, but this was the spec, continue the outer loop if the inner loop gets broken.
Here’s a better example with date parsing:
# Try to parse a timestamp with a bunch of formats
for fmt in (JSON, PG, ISO, RFC, HUMAN):
try:
ts = datetime.strptime(ts, fmt)
break
except ValueError:
continue
else:
# Could not parse with any of the formats required
raise ValueError("could not parse timestamp")
Is this better or worse than the function version of this?
def parse_timestamp(ts):
for fmt in (JSON, PG, ISO, RFC, HUMAN):
try:
return datetime.strptime(ts, fmt)
except ValueError:
continue
raise ValueError("could not parse timestamp")
ts = parse_timestamp(ts)
Let’s go to the benchmarks:
So basically, there is no meaningful difference, but depending on the context of implementation, using for/else
may be a bit more meaningful or easy to test than having to implement another function.
Benchmark code can be found here.