1

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.

2
  • Could you provide a concrete example of what you're trying to do? What are the contents of list? What is some_condition? What is do this? What is x.method()? What is do this to x? What is do something else? Exactly what output do you expect to see? Commented Mar 31, 2015 at 18:35
  • @Kevin I added another clarification of the concept, maybe the clearest so far, I guess. Commented Mar 31, 2015 at 18:45

5 Answers 5

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Very true, and that's what was already suggested. I did totally forget about that one.
2

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"

3 Comments

If you take a look at the last clarification I added to the question (which I think is the clearest so far), would you say it's not possible? But thanks for the for ... else, I totally forgot about that one (naturally). It's close enough to what I need.
@tobias_k Just an update, I have tried second case in python 3.6, it is not working
@BhargavKatkam Right, that "leaking out of a list comprehension" thing was fixed in Python 3. Note, however, that it did not "work" in Python 2, either, as x will have the last value from the list, which is evaluated entirely before the any is applied.
1
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.

2 Comments

This does not give you a reference to x, so you basically would have to do it all again to find the right x
Yes, what I would like to have is any() but with a reference to x.
0

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

7 Comments

This will execute x.method for all x
So would the syntax that the question proposed, if it worked.
But if you read the question and examine the examples, you see that OP does not want to execute it for each x and that he breaks after the first match.
It is not clear to me, but anyway I have added a variant that does not have this issue.
I thought of that try/except hack, too, but you do not need dropwhile. Just do next(x for x in list if x.method())
|
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.