Roope
Roope

Reputation: 4507

Python: loop in elif condition

I'm running Python 3.

Is it possible to put a loop in the condition for elif? Here's the basic idea I'm trying to achieve. The number of items in the list is not constant.

if some_condition:
    do this
elif [ x.method() for x in list ]:
    do this to x
else:
    do something else

Now, this comes to my mind:

if some_condition:
    do this
for x in list:
    if x.method():
        do this to x
        break

But I'm trying to avoid running all the if statements, there's a lot of stuff going on in them. And I would like to get it in the elif part specifically and not in else.

Edit / more clarification:

It seems what I would need is any( x.method() for x in list ) but also with a reference to x so that I can then use x if the condition was true.

Here's the whole concept I'm trying to get again:

if condition:
    do this
elif list[0].method():
    do this to list[0]
elif list[1].method():
    do this to list[1]
...
elif list[n].method():
    do this to list[n]
else:
    do this

where method() is some method that returns True or False, and n is the size of the list and not a constant.

Upvotes: 1

Views: 4445

Answers (5)

Chad Miller
Chad Miller

Reputation: 1475

I see now. The Pythonic way of doing what your updated edit says is to make a for loop and break out after you find your first item and act on it, and use the almost unknown and unused "else" of a loop.

Since the "for / else" idiom is so rarely used, you will need to add a comment about it, for future readers.

if some_test:
    first_action()
else:
    for item in list:
        if item.method():
            do_this(item)
            break   # process no more of list
    else:   # for-else only entered if no break from the list!
        final_action()

Read up on loop elses in the Python Tutorial.

Upvotes: 3

Matt
Matt

Reputation: 744

This could work as long as you don't need a reference to x outside of the elif block. This feels closest to the syntax you are asking for, but I would still probably go with either Kevin or Tobias's answers as they are more explicit and readable.

 def check_and_do(x):
    b = x.method()
    if b:
        do this to x
    return b

if condition:
    do this
elif any(check_and_do(x) for x in list):
    pass  # Already did this to x in check_and_do
else:
    do something else

Upvotes: 0

tobias_k
tobias_k

Reputation: 82899

I don't think what you want -- to have it entirely in the elif -- is possible. You'd have to evaluate whether there is any such value in the list, and then bind it to x in the condition. As far as I know, this is not possible in Python's syntax. You can not do an assignment in the condition, and while the loop variable in a list comprehension can "leak" to the outside scope, the same is not true for a generator.

>>> if any(x for x in range(10) if x >= 5):
...     print x
NameError: name 'x' is not defined
>>> if any([x for x in range(10) if x >= 5]):
...     print x
9

In the second case (list), we have a reference to x, but it is the last value from the entire list, and in the first case (generator), x can not be resolved at all.


Instead, here's another variant, using a generator expression to combine the for with the if, and adding an else to the for to enumate your final else clause.

if some_condition:
    print "do this"
else:
    for x in (x for x in lst if foo(x)):
        print "do this to", x
        break
    else:
        print "do something else"

Upvotes: 2

TheBlackCat
TheBlackCat

Reputation: 10298

It is not exactly possible to do what you want, but you can do something similar with list comprehensions:

if some_condition:
    do this
else:
    res = [x for x in list if x.method()]
    if res:
        do something to res[0]
    else:
        do something else

To avoid do x.method() for every value, you could also use itertools.dropwhile. The following approach will keep checking x.method() until it finds one that is true, then stop and return just that value of x. If it doesn't find any that are true, it will do something else.

from itertools import dropwhile
if some_condition:
    do this
else:
    try:
        res = next(dropwhile(lambda x: not x.method(), x))
        do something to res
    except StopIteration:
        do something else

Upvotes: 0

Chad Miller
Chad Miller

Reputation: 1475

if [ x.method() for x in list ]:

You ask, is it possible? Yes. What does it do, though? Probably not what you intend.

For the number of items in list, it will fabricate one value in the resulting list. For list values, only a list of zero length will be False. [False, False, False] is True, so the body of the condition will be executed.

You may be looking for all() or any() if x.method() is supposed to influence the following condition decision.

if all(x.method() for x in list):   # will require that every *x.method* call return truth.

Likewise, you may want

if any(x.method() for x in list):   # only one required. May not do every .method if it can stop early. Beware side-effects.

Upvotes: 1

Related Questions