Search code examples
pythonif-statementnestedconditional-statements

Avoiding nested if else statement ladder in Python


It's hard to find answers to this problem when you don't know exactly how to describe it...

What is the most idiomatic way to deal with a fairly deep (but fixed) nested set of tests that you want to run sequentially but terminate as soon as the first one comes up with a successful result?

Instead of the following

Option 1: (results in way too much indentation)

def make_decision():

    results = ... some code or function that returns a list or None
    if results:
        decision = random.choice(results)
    else:
        results = ... other code or function
        if results:
            decision = random.choice(results)
        else:
            results = ... other code or function
            if results:
                decision = random.choice(results)
            else:
                results = ... other code or function
                if results:
                    decision = random.choice(results)

      ...etc.

                                else:
                                    decision = None

    print(decision)

    return decision

Option 2

Another option is to return from the function early but I'm not sure it's good practice to have so many returns scattered about and in this case I would prefer to do one final thing at the end (e.g. print(decision)) before returning:

def make_decision():

    results = ... some code or function that returns a list or None
    if results:
        return random.choice(results)
    
    results = ... other code or function
    if results:
        return random.choice(results)

    results = ... other code or function
    if results:
        return random.choice(results)
    
    ...etc.

    return None

Option 3

Finally I could make each test a separate function as described here and call them iteratively, provided each test function has the same set of arguments.

def test1(args):
    ...

def test2(args):
    ...

def test3(args):
    ...

def make_decision():

    decision = None
    for test in [test1, test2, test3, ...]:
        results = test(args)
        if results:
            decision = random.choice(results)
            break

    print(decision)

    return decision

This looks the best but I hadn't planned to make a function for every test and some tests can be done with the same function but with different arguments and some are just one-liners whereas others are multiple lines. So then would I have to build a list of functions and arguments before starting the loop? Or make a list of partial functions?

Any better suggestions welcome (before I go ahead with option 3 above)

UPDATE 2018-07-21:

A future potential option

Unbeknown to me as I was wrestling with this problem, PEP 572 was approved (and Mr van Rossum resigned as a consequence). If this PEP is implemented the following solution will also be possible I think:

def make_decision():

    if (results := ... some code or function) is not None:
        decision = random.choice(results)
    elif (results := ... some code or function) is not None:
        decision = random.choice(results)
    
    ...etc.

    else:
        decision = None

    return decision

Solution

  • Sorry, this is obvious now that re-look at it:

    def make_decision():
    
        results = ... some code or function that returns a list or None
    
        if results is None:
            results = ... other code or function
    
        if results is None:
            results = ... other code or function
    
        ...etc.
    
        if results is None:
            decision = None
        else:
            decision = random.choice(results)
        print(decision)
    
        return decision