0

I'm trying to iterate elif statements over lists with an else statement at the end. Here is my code:

 if clickPoint is None:
 print(clickPoint)
 for each in meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 for each in build_meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 for each in ingredient_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 else:
 print(clickPoint)

meal_objects, build_meal_objects, and ingredient_objects are lists.

The problem is that this code is horrible for many reasons. If the first condition is not met, each for loop will run even if one of the for loops conditions have already run, and the final else statement will also be running. Really, if any of the if statements within any of the for loops is met, then the rest of the if block should not execute.

The code should really be something more like this pseudocode:

 if clickPoint is None:
 print(clickPoint)
 elif for each in meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 elif for each in build_meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 elif for each in ingredient_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
 else:
 print(clickPoint)

I feel like I might be overlooking something really simple, so forgive me if that is the case or if this is a poorly written question. Thanks!

Vidya Sagar
1,52614 silver badges23 bronze badges
asked Jun 24, 2017 at 10:17
3
  • You can't have an elif for. You could use a variable to keep track of whether one of the loops ran, and then only do the next loop if it did. Commented Jun 24, 2017 at 10:20
  • By "ran," do you mean it called each._button.setFill("green")? Commented Jun 24, 2017 at 10:22
  • 1
    Wrap them in a function and instead of break use a return is one way. Commented Jun 24, 2017 at 10:23

4 Answers 4

1

Probably the simplest (and most flexible way) is to put them in a function and force a return, eg:

def f(clickPoint, *lists):
 if clickPoint is None:
 # or raise an exception instead as seems more an exception than natural
 return (None, None)
 for lst in lists:
 for item in lst:
 if inside(clickPoint, item._button):
 item._button.setFill('green')
 return (lst, item)
 return (None, None)

Then call it as:

lst, item = f(clickPoint, meal_objects, build_meal_objects, ingredient_objects)

This means that only the first element of all lists will have a fill set and it returns a reference to the list and the item that was set which you can check later if you want to do the print.

eg:

if (lst, item) == (None, None):
 # handle that nothing was set?
else:
 # you know which button (`item`) in which list (`lst`) had its fill changed 

I suppose if you really had a need to, you can make use of the for/else syntax that Python has but that requires you to effectively chain the sequences to a single for to issue the break on, eg:

for lst, item in ((lst, item) for lst in (meal_objects, build_meal_objects, ingredient_objects) for item in lst):
 if inside(clickPoint, item._button):
 item._button.setFill('green')
 break
else: # this only enters if `break` was NOT issued in the for-loop
 print('nothing set')
answered Jun 24, 2017 at 10:33
Sign up to request clarification or add additional context in comments.

Comments

1

Ok this is going to need some explanation. Take a look at this below.

for x in range(5):
 for y in range(5):
 print (x*y)
 if x*y==3:
 break
 else:
 continue # executed if the loop finished normally (no break)
 break # executed if 'continue' was skipped (break)

The above program just prints values till it finds a 3.

output:

0
0
0
0
0
0
1
2
3

What if the last continue and else weren't present?

for x in range(5):
 for y in range(5):
 print (x*y)
 if x*y==3:
 break

output:

0
0
0
0
0
0
1
2
3
0
2
4
6
8
0
3
0
4
8
12
16

See it doesn't even stop after finding a 3 that's because the break only caused to exit out of the inner loop. So the first code can be used to exit out of a nested loop. Even a very deeply nested loop.

So how to apply this in your code? Look at this!

my_objects = {0: meal_objects, 1: build_meal_objects, 2: ingredient_objects}
flag2=True
flag1=True
if clickPoint is None:
 print(clickPoint)
 flag2=False
if flag2:
 for i in range(3):
 temp_obj = my_objects[i]
 for each in temp_obj:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 flag1=False
 break
 else:
 continue
 break
if flag1 and flag2:
 print(clickPoint)

Use flag variables. Now in the above code first set flag1=True and flag2=True. The first ifblock gets executed and if clickPoint is None then it is printed. And flag2 is set to be False.

Why did I do that? To ensure that it fails in the next if statement. Thereby none of your for loops are executed.

The next part if incase clickPoint is not None. The next if is executed and note here is where I did a small change!

my_objects = {0: meal_objects, 1: build_meal_objects, 2: ingredient_objects}

Create a dict of your objects and get them one by one using range(your_dict_size). NOTE This way you can add even more and more objects.

Remember I explained about exiting out of nested loops? That's exactly what happens. The moment you get what you wanted that's it control exits and no more for loops are run.

and set flag1 to False

NOTE: The reason for two flag variables is because to ensure your else part (Here it is the last if) always executes if the first two ifs fail.

answered Jun 24, 2017 at 11:50

Comments

0

I think this is what you're trying to do:

if clickPoint is None:
 print(clickPoint)
else:
 called = False
 for each in meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 called = True
 break
 if not called:
 for each in build_meal_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 called = True
 break
 if not called:
 for each in ingredient_objects:
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 called = True
 break
 if not called:
 print(clickPoint)

This makes sure that only one of the for loops calls each._button.setFill('green'). If none of them calls it, then the print statement runs.

answered Jun 24, 2017 at 10:30

Comments

0

Seems like you might was well use itertools.chain to condense the for loops into one block. This way you loop over all the items in your lists and stop as soon as you get to the first one that is "inside".

from itertools import chain
if clickPoint is None:
 print(clickPoint)
for each in chain(meal_objects, build_meal_objects, ingredient_objects):
 if inside(clickPoint, each._button):
 each._button.setFill('green')
 break
else:
 print(clickPoint)
answered Jun 24, 2017 at 12:07

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.