-2

say you have 10+ conditions that need to be true is there a better way to write:

if condition1 and condition2 and condition3 and condition4.......:
    return value

with the added stipulation that they can't all be evaluated ahead of time and must be run in order. condition1 needs to be true for condition2 to run, otherwise it will error.

what I'm trying to prevent is

test = False 
if all([test, test.func(returns either true or false)]): 
    pass 
= AttributeError: 'bool' object has no attribute 'func' 

mimic this functionality

if condition1:
    if condition2:
       ......
        if condition13:
            return True
lonewarrior556
  • 3,917
  • 2
  • 26
  • 55
  • 2
    What are the condtiions? – Padraic Cunningham May 29 '15 at 22:46
  • Are you trying to check if a name exists, then check that it has certain attributes etc...? It's somewhat ambigious – Jon Clements May 29 '15 at 22:52
  • Still unclear... are you always guaranteed to have `test.func()` ? And if you don't - does that mean it's `False`? – Jon Clements May 29 '15 at 22:56
  • sometimes test will be just False, sometime it will be an object – lonewarrior556 May 29 '15 at 22:57
  • 1
    And when it's an object... do they behave properly in a boolean sense...? I'd probably stick in a nicely formatted `if test and getattr(test, 'func', False) and...`- that'd get the truthinesss of the value if present without throwing an exception – Jon Clements May 29 '15 at 22:59
  • 1
    Or even just wrap it in a try, and list line by line your conditions... but again - hard to say what you want because it's still way too vague – Jon Clements May 29 '15 at 23:04
  • 1
    what is test in your code? – Padraic Cunningham May 29 '15 at 23:11
  • Even with your updated question it's still too vague because we still don't know what `test` is, and when and what it is reassigned to it. But either way, you should be able to adapt my generator example to your needs, whatever they are. – Lukas Graf May 29 '15 at 23:12

1 Answers1

3

You can use the all() builtin (which takes an iterable of expressions):

if all([condition1, condition2, ...]):
    return value

The expressions will be evaluated in order (since all() expects an iterable), so from left to right in this example with a list.

As soon as all() encounters the first expression that evaluates to False it will short circuit and stop pulling expressions from the iterable. If your expressions are expensive to evaluate, you can make use of this by passing it a generator instead of a pre-built sequence.


Here's a more advanced example using a generator in order to make use of the short circuiting to avoid evaluating later expressions:

def fail():
    raise Exception("Boom")

def conditions():
    yield 1 == 1
    yield 2 == 42
    yield fail()

if all(conditions()):
    print "All True"
else:
    print "Not all True"

So calling conditions() will return a generator object. Iterating over that generator will cause the code in that function to run up to the first yield statement, then evaluate the expression after that yield return the result as the first item. Then the code in the generator is paused until all() pulls the next value, which will resume the code and run it until the next yield statement is encountered, etc.

Therefore this will never call fail() because after 2 == 42 will evaluate to False and all() will stop iterating over the generator.

Community
  • 1
  • 1
Lukas Graf
  • 30,317
  • 8
  • 77
  • 92
  • 1
    It won't short circuit as the conditions will all be evaluated already – Padraic Cunningham May 29 '15 at 22:36
  • Doesn't `if a and b and c and d ...` also stop as soon as it hits a `False`, because then the whole thing is false. Just as `if a or b or c or d ...` stops as soon as it hits a `True`. Correct? – Deacon May 29 '15 at 22:37
  • @PadraicCunningham if you pass in conditions that already *have* been evaluated like in this simple example, of course. But it could also take a generator or an iterator that yields expressions dynamically, and `all()` will stop iterating as soon as it encounters the first expression that evaluates to `False`. – Lukas Graf May 29 '15 at 22:40
  • @LukasGraf - That's the reason I was taught as a rule of thumb to organize the items in an `and` statement from most to least likely to **fail** and the items in an `or` statement from most to least likely to **succeed**. I have know idea whether that's a good practice or not, but it's stuck with me over time. :-D – Deacon May 29 '15 at 22:47
  • yea it doesn't short circit what im trying to prevent is test = false if all([test, test.func]): pass ...... AttributeError: 'bool' object has no attribute 'func' – lonewarrior556 May 29 '15 at 22:48
  • You can use this to lazily evaluate by using something like: `if all(condition() for condition in (condition1, condition2, condition3))` – Jon Clements May 29 '15 at 22:49
  • @lonewarrior556 I don't quite get what you're trying to do. If `test` is a boolean, it can obviously never have an attribute `func`, unless something else is assigned to `test` at some point. Could you please edit your question and give an example of your code and the checks you're doing? – Lukas Graf May 29 '15 at 22:57