【问题标题】:Python If/Elif/Else, For and ListsPython If/Elif/Else、For 和列表
【发布时间】:2017-11-27 20:29:51
【问题描述】:

我正在尝试在列表中迭代 elif 语句,并在末尾添加一个 else 语句。这是我的代码:

    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 和 ingredients_objects 是列表。

问题是这段代码很糟糕,原因有很多。如果第一个条件不满足,即使其中一个 for 循环条件已经运行,每个 for 循环也会运行,最后的 else 语句也将运行。实际上,如果满足任何 for 循环中的任何 if 语句,则不应执行 if 块的其余部分。

代码应该更像这个伪代码:

    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)

我觉得我可能忽略了一些非常简单的事情,如果是这种情况或者这是一个写得不好的问题,请原谅我。谢谢!

【问题讨论】:

  • 您不能拥有elif for。您可以使用变量来跟踪其中一个循环是否运行,然后仅在运行时执行下一个循环。
  • “跑”是指each._button.setFill("green")吗?
  • 将它们包装在一个函数中,而不是 break 使用 return 是一种方法。

标签: python list for-loop if-statement


【解决方案1】:

可能最简单(也是最灵活的方式)是将它们放在一个函数中并强制一个return,例如:

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)

然后称它为:

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

这意味着只有所有列表的第一个元素会有一个填充集,它会返回一个对列表的引用和设置的项目,如果你想打印,你可以稍后检查。

例如:

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

我想如果你真的有需要,你可以使用 Python 拥有的 for/else 语法,但这需要你有效地将序列链接到单个 for 以发出 break,例如:

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')

【讨论】:

    【解决方案2】:

    好的,这需要一些解释。看看下面这个。

    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)
    

    上面的程序只打印值直到找到3

    输出:

    0
    0
    0
    0
    0
    0
    1
    2
    3
    

    如果最后一个 continueelse 不存在怎么办?

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

    输出:

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

    发现3 之后它甚至不会停止,这是因为break 只是导致退出内部循环。所以第一个代码可以用来退出嵌套循环。甚至是非常深的嵌套循环。

    那么如何在你的代码中应用它呢?看看这个!

    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)
    

    使用标记变量。现在在上面的代码中首先设置flag1=Trueflag2=True。第一个 ifblock 被执行,如果 clickPoint 为 None 则打印。 flag2 设置为False

    我为什么要这样做?确保它在下一个if 语句中失败。因此,您的任何 for 循环都不会被执行。

    如果clickPoint 不是None,则下一部分。下一个if 被执行,注意这里是我做了一个小改动的地方!

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

    为您的对象创建一个dict,并使用range(your_dict_size) 逐一获取它们。 注意这样您可以添加越来越多的对象。

    还记得我解释过退出嵌套循环吗?这正是发生的事情。当你得到你想要的就是它控制退出并且不再运行for循环的那一刻。

    并将 flag1 设置为 False

    注意:两个flag变量的原因是为了确保你的else部分(这里是最后一个if)总是在前两个执行时执行ifs 失败。

    【讨论】:

      【解决方案3】:

      我认为这就是你想要做的:

      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)
      

      这确保只有一个 for 循环调用 each._button.setFill('green')。如果他们都没有调用它,那么 print 语句就会运行。

      【讨论】:

        【解决方案4】:

        似乎您可以很好地使用itertools.chain 将 for 循环压缩为一个块。这样,您可以遍历列表中的所有项目,并在到达“内部”的第一个项目时立即停止。

        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)
        

        【讨论】: