Option #1: Using or
When the number of total functions is a) known, and b) small, and the test condition is based entirely on the truth value of the return, it's possible to simply use or
as Grapsus suggested:
d = 'somedata'
result = f1(d) or f2(d) or f3(d) or f4(d)
Because Python's boolean operators short-circuit, the functions are executed from right to left until one of them produces a return value evaluated as True
, at which point the assignment is made to result
and the remaining functions are not evaluated; or until you run out of functions, and result
is assigned False
.
Option #2: Using generators
When the number of total functions is a) unknown, or b) very large, a one-liner generator comprehension method works, as Bitwise suggested:
result = (r for r in (f(somedata) for f in functions) if <test-condition>).next()
This has the additional advantage over option #1 that you can use any <test-condition>
you wish, instead of relying only on truth value. Each time .next()
is called:
- We ask the outer generator for its next element
- The outer generator asks the inner generator for its next element
- The inner generator asks for an
f
fromfunctions
and tries to evaluatef(somedata)
- If the expression can be evaluated (i.e.,
f
is a function andsomedata
is a valid arugment), the inner generator yields the return value off(somedata)
to the outer generator - If
<test-condition>
is satisfied, the outer generator yields the return value off(somedata)
and we assign it asresult
- If
<test-condition>
was not satisfied in step 5, repeat steps 2-4
A weakness of this method is that nested comprehensions can be less intuitive than their multi-line equivalents. Also, if the inner generator is exhausted without ever satisfying the test condition, .next()
raises a StopIteration
which must be handled (in a try-except block) or prevented (by ensuring the last function will always "succeed").
Option #3: Using a custom function
Since we can place callable functions in a list, one option is to explicitly list the functions you want to "try" in the order they should be used, and then iterate through that list:
def analyse(somedata):
analysis_functions = [best, okay, poor]
for f in analysis_functions:
result = f(somedata)
if result:
return result
Advantages: Fixes the problem of repeated code, it's more clear that you're engaged in an iterative process, and it short-circuits (doesn't continue executing functions after it finds a "good" result).
This could also be written with Python's for ... else
syntax:*
def analyse(somedata):
analysis_functions = [best, okay, poor]
for f in analysis_functions:
result = f(somedata)
if result:
break
else:
return None
return result
The advantage here is that the different ways to exit the function are identified, which could be useful if you want complete failure of the analyse()
function to return something other than None
, or to raise an exception. Otherwise, it's just longer and more esoteric.
*As described in "Transforming Code into Beautiful, Idiomatic Python", starting @15:50.