The same test written both ways. Note where each one stops.
numbers = [1, 3, 4, 5, 6]
found = False
for n in numbers:
if n % 2 == 0:
found = True # keeps going: tests 5 and 6 as well
print(any(n % 2 == 0 for n in numbers)) # stops at 4
- any() short-circuits
It pulls values from the generator until one is true and then stops asking. The flag version has no way to stop unless you add a break, and adding a break to a loop that also sets a flag is two things to keep in step instead of none.
- The name states the intent
`any(...)` reads as the question being asked. A flag called found, or worse flag or result, makes the reader assemble the meaning from three lines. Code that says what it means is easier to review than code that says how it works.
- all() is the other half
any() is true when at least one is; all() is true when none fail — and all() of an empty sequence is True, which surprises people. Both take any iterable, so a generator expression keeps them lazy.
Read the loop out loud. It says 'is there at least one invoice where…' — and Python has a built-in whose name is that question.
any(condition for item in items) is True when the condition holds for at least one item. The condition here is the same one already inside the if.
One return. any() over a generator expression that yields the comparison for each invoice. No flag, no loop body, and no final return of a variable.
def has_overdue(invoices): return ____(invoice['days_late'] > 0 for invoice in ____)
def has_overdue(invoices):
return any(invoice["days_late"] > 0 for invoice in invoices)
any([...]) with square brackets would build the whole list of booleans first and then look for a True — every invoice tested, exactly like the flag version. Dropping the brackets makes it a generator, and any() then stops at the first match. One character of difference and the whole improvement lives in it.
any([]) is False and all([]) is True. The second one catches people out: 'all invoices are paid' is vacuously true when there are no invoices. Both are the mathematically correct answers and both are worth checking against what your caller actually means.
If you need the item rather than the fact — which invoice is overdue — this is first_match, not any(). And if the loop does something else on the way past, logging or counting, it is doing two jobs and any() only replaces one of them. The refactor applies when the loop exists purely to answer yes or no.
any() answers a question and gives you nothing else. If the next line needs the matching item, use next() with a default; if it needs how many matched, use sum() over the same generator. Reaching for any() and then looping again to find the item is two passes where one would do — the refactor is about saying what you mean, not about using the built-in wherever it fits.
- Write has_any_paid using all() instead, and work out what your function should return for an empty list — then check whether that matches what a caller would assume.
- Add a predicate parameter so the caller supplies the condition, and notice you have rebuilt part of first_match.
- Count the overdue invoices with sum() over the same generator, and explain why that cannot short-circuit.