1

Say there's a kind of function that returns either some value or None, and we have three such functions: egg, ham and spam.

The goal is to call egg, ham, spam and return the first non-None value returned, and if there's no valid value return, return None.

The most straightforward way would be:

def func():
    ret = egg()
    if ret:
        return ret
    ret = ham()
    if ret:
        return ret
    ret = spam()
    if ret:
        return ret
    return None

or, a smarter but maybe harder to read solution is:

def func():
    return egg() or ham() or spam()

Here're my questions (there could be more than three candidate functions to call):

  1. Is the second way hard to read and should be avoided?
  2. Is there a better way to design such control flow? I remember there's something in Lisp that does exactly this, but what about in Python?
Uduse
  • 1,491
  • 1
  • 13
  • 18
  • The second one actually is far more readable, in my view. – sytech Mar 07 '18 at 06:18
  • @sytech I agree if it is a handful, 2-3 ideally. Much more than that and the only reasonable thing to do is to write a function for it. – juanpa.arrivillaga Mar 07 '18 at 06:22
  • If you want a really unreadable solution, how about `next(itertools.dropwhile(lambda x: not x, (f() for f in [egg, ham, spam])))` Although that may be favorable if you have a large set of functions. – sytech Mar 07 '18 at 06:30
  • @sytech I thought about using `next` + generator with if, but I couldn't avoid evaluating the valid return value twice. Didn't know dropwhile even exists XD – Uduse Mar 07 '18 at 06:39
  • 1
    @Uduse you can do something like `next(r for f in (eggs, ham, spam) for r in (f(),) if r)` but it's hardly elegant. And really, you should be doing `next((r for f in (eggs, ham, spam) for r in (f(),) if r), None)` to be safe – juanpa.arrivillaga Mar 07 '18 at 06:52
  • 0 and False would fail and are not None. So strictly speaking you wouldn't necessarily return the first non-None value with either of those. The first "truthy" value, but not the first non-None. But if that's what you're after, then I don't think most people would find the second hard to read. You could look at an example of a coalesce function at https://stackoverflow.com/questions/4978738/is-there-a-python-equivalent-of-the-c-sharp-null-coalescing-operator for a check for None – clockwatcher Mar 07 '18 at 07:17

2 Answers2

0

As this requirement is very similar to the builtin any() I would write:

def anyval(iterable):
    r = None
    for r in iterable:
        if callable(r):
            r = r()
        if r != None:
            break
    return r


def foo():
    return None

def bar():
    return 0

def baz(x):
    return x * 3.14

print(anyval([None,foo,bar]))
print(anyval([None,lambda: baz(3.14)]))
Vroomfondel
  • 2,704
  • 1
  • 15
  • 29
0

Something like:

def sifter():
  funcs = [eggs, ham, spam]
  result = None
  while funcs and result is None:
    result = funcs.pop(0)()
  return result

A little explanation: assuming, as in the question, that all the functions to run accept no arguments the while funcs and result is None bit ensures there are more functions to try and that the current result is still None. in the while loop we:

  1. Pop the first item from the list of funcs funcs.pop(0). (The list of funcs then looses that func)
  2. call the poped function ()
  3. Assign what is returned from the function call to the name result =

Ready for the next loop through the while statement.

Paddy3118
  • 4,704
  • 27
  • 38
  • How's the explanation @Uduse? – Paddy3118 Mar 08 '18 at 09:25
  • it's hard to track the flow of `result` in a skim. When I say "really hard to read" I mean it's hard to grasp what's going on in 5 seconds. However, in terms of succinctness, I think you're doing a great job. – Uduse Mar 09 '18 at 02:15
  • Hi @Uduse, I didn't try to be too compact, An intermediate programmer should be able to grasp this, but it does do a pop, a function call, and an assignment in one statement. You could introduce a tmp variable to take the popped function, then call the tmp function and assign to result in another separate line if it is too complex for your needs. – Paddy3118 Mar 09 '18 at 11:32