Search code examples
pythonlazy-evaluationanyshort-circuiting

How can I make the short-circuiting of Python's any() and all() functions effective (avoid evaluation before the function call)?


Python's any and all built-in functions are supposed to short-circuit, like the logical operators or and and do.

However, suppose we have a function definition like so:

def func(s):
    print(s)
    return True

and use it to build a list of values passed to any or all:

>>> any([func('s'), func('t')])
's'
't'
True

Since the list must be constructed before any is called, the function is also evaluated ahead of time, effectively preventing the short-circuiting.

If the function calls are expensive, evaluating all the functions up front is a big loss and is a waste of this ability of any.

Knowing that any accepts any kind of iterable, how can we defer the evaluation of func, so that the short-circuiting of any prevents the call of func(t)?


Solution

  • We can use a generator expression, passing the functions and their arguments separately and evaluating only in the generator like so:

    >>> any(func(arg) for arg in ('s', 't'))
    's'
    True
    

    For different functions with different signatures, this could look like the following:

    any(
        f(*args)
        for f, args in [(func1, ('s',)), (func2, (1, 't'))]
    )
    

    That way, any will stop iterating over the generator as soon as one function call evaluates to True, and that means that the function evaluation is fully lazy.

    Another neat way to postpone the function evaluation is to use lambda expressions, like so:

    >>> any(
    ...     f() 
    ...     for f in [lambda: func('s'), lambda: func('t')]
    ... )
    's'
    True