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!
4 Answers 4
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')
Comments
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.
Comments
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.
Comments
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)
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.each._button.setFill("green")?breakuse areturnis one way.