【问题标题】:Pythonic ways to use 'else' in a for loop在 for 循环中使用“else”的 Pythonic 方法
【发布时间】:2009-03-26 13:22:32
【问题描述】:

我几乎没有注意到在 for 循环中使用 else 的 python 程序。

我最近用它在退出时根据循环变量条件执行一个动作;因为它在范围内。

在 for 循环中使用 else 的 Pythonic 方式是什么?有什么值得注意的用例吗?

而且,是的。我不喜欢使用 break 语句。我宁愿将循环条件设置为复杂。如果我不喜欢使用 break 语句,我能从中获得什么好处吗?

值得注意的是,自语言诞生以来,for 循环就有一个 else,这是有史以来的第一个版本。

【问题讨论】:

  • 嗯,很有趣。 for-else 语句是 Python 特有的东西还是其他语言有它?我以前从未听说过它(但我从未使用过 Python,只是听说过很多关于它的好东西)
  • 我正在考虑如何在现存的 python 源代码中搜索这个结构的用途,看看它是否存在于 SO 之外。

标签: for-loop python


【解决方案1】:

还有什么比 PyPy 更 Python 的呢?

看看我从 ctypes_configure/configure.py 的第 284 行开始发现了什么:

    for i in range(0, info['size'] - csize + 1, info['align']):
        if layout[i:i+csize] == [None] * csize:
            layout_addfield(layout, i, ctype, '_alignment')
            break
    else:
        raise AssertionError("unenforceable alignment %d" % (
            info['align'],))

在这里,从 pypy/annotation/annrpython.py (clicky) 的第 425 行开始

if cell.is_constant():
    return Constant(cell.const)
else:
    for v in known_variables:
        if self.bindings[v] is cell:
            return v
    else:
        raise CannotSimplify

在 pypy/annotation/binaryop.py 中,从第 751 行开始:

def is_((pbc1, pbc2)):
    thistype = pairtype(SomePBC, SomePBC)
    s = super(thistype, pair(pbc1, pbc2)).is_()
    if not s.is_constant():
        if not pbc1.can_be_None or not pbc2.can_be_None:
            for desc in pbc1.descriptions:
                if desc in pbc2.descriptions:
                    break
            else:
                s.const = False    # no common desc in the two sets
    return s

pypy/annotation/classdef.py 中的非单行代码,从第 176 行开始:

def add_source_for_attribute(self, attr, source):
    """Adds information about a constant source for an attribute.
    """
    for cdef in self.getmro():
        if attr in cdef.attrs:
            # the Attribute() exists already for this class (or a parent)
            attrdef = cdef.attrs[attr]
            s_prev_value = attrdef.s_value
            attrdef.add_constant_source(self, source)
            # we should reflow from all the reader's position,
            # but as an optimization we try to see if the attribute
            # has really been generalized
            if attrdef.s_value != s_prev_value:
                attrdef.mutated(cdef) # reflow from all read positions
            return
    else:
        # remember the source in self.attr_sources
        sources = self.attr_sources.setdefault(attr, [])
        sources.append(source)
        # register the source in any Attribute found in subclasses,
        # to restore invariant (III)
        # NB. add_constant_source() may discover new subdefs but the
        #     right thing will happen to them because self.attr_sources
        #     was already updated
        if not source.instance_level:
            for subdef in self.getallsubdefs():
                if attr in subdef.attrs:
                    attrdef = subdef.attrs[attr]
                    s_prev_value = attrdef.s_value
                    attrdef.add_constant_source(self, source)
                    if attrdef.s_value != s_prev_value:
                        attrdef.mutated(subdef) # reflow from all read positions

稍后在同一个文件中,从第 307 行开始,一个带有启发性注释的示例:

def generalize_attr(self, attr, s_value=None):
    # if the attribute exists in a superclass, generalize there,
    # as imposed by invariant (I)
    for clsdef in self.getmro():
        if attr in clsdef.attrs:
            clsdef._generalize_attr(attr, s_value)
            break
    else:
        self._generalize_attr(attr, s_value)

【讨论】:

  • classdef.py:176 中的 else: 子句是不必要的,因为它们通过“return”退出循环。顺便说一句,您是如何找到这些的?只是盯着源头,还是更智能的东西?
  • grep for (for|else) 在十行以内的相同缩进级别。如果我有海参的技能,我会找出在 PyPy 编译器中 for ... else 流控制发生的位置,添加日志记录,然后构建 -all。
【解决方案2】:

如果你有一个 for 循环,你实际上并没有任何条件语句。因此,如果您想中止,那么 break 是您的选择,然后可以完美地处理您不开心的情况。

for fruit in basket:
   if fruit.kind in ['Orange', 'Apple']:
       fruit.eat()
       break
else:
   print 'The basket contains no desirable fruit'

【讨论】:

  • 你误解了这个概念。中断后不执行 else 块。
  • 你错了,因为 else 部分只有在 for 完成时才会执行。
  • @Ferdinand:也许他的解释并不完美,但从代码中你可以清楚地看到,这正是他的意思。
  • @vartec:好的,你是对的。对我来说,它读起来像:使用 else 块,您可以检查您中断的原因。我的错。
  • 好吧,如果你的钱包里没有足够的总钱,它不会坏,因此它会说你没有足够的钱。如果钱包完全空了,也会执行 else 语句。
【解决方案3】:

基本上,它简化了任何使用布尔标志的循环,如下所示:

found = False                # <-- initialize boolean
for divisor in range(2, n):
    if n % divisor == 0:
        found = True         # <-- update boolean
        break  # optional, but continuing would be a waste of time

if found:                    # <-- check boolean
    print n, "is composite"
else:
    print n, "is prime"

并允许您跳过标志的管理:

for divisor in range(2, n):
    if n % divisor == 0:
        print n, "is composite"
        break
else:
    print n, "is prime"

请注意,当您找到除数时,已经有一个自然的地方可以执行代码 - 就在 break 之前。这里唯一的新功能是在您尝试所有除数但未找到任何除数时执行代码的地方。

这仅有助于与 break 结合使用。如果你不能中断,你仍然需要布尔值(例如,因为你在寻找最后一场比赛,或者必须并行跟踪多个条件)。

哦,顺便说一句,这也适用于 while 循环。

任何/所有

现在,如果循环的唯一目的是回答“是”或“否”,您可以使用 any()/all() 函数和生成布尔值的生成器或生成器表达式将其写得更短:

if any(n % divisor == 0 
       for divisor in range(2, n)):
    print n, "is composite"
else:
    print n, "is prime"

注意优雅!代码是你想说的1:1!

[这与带有break 的循环一样有效,因为any() 函数是短路的,只运行生成器表达式直到它产生True。事实上,它通常比循环更快。更简单的 Python 代码往往不会被偷听。]

如果您有其他副作用 - 例如,如果您想找到除数,这不太可行。您仍然可以(ab)使用 Python 中的非 0 值为真这一事实:

divisor = any(d for d in range(2, n) if n % d == 0)
if divisor:
    print n, "is divisible by", divisor
else:
    print n, "is prime"

但正如您所见,这越来越不稳定 - 如果 0 是可能的除数值,则将无法正常工作...

【讨论】:

    【解决方案4】:

    不使用breakelse 块对forwhile 语句没有好处。下面两个例子是等价的:

    for x in range(10):
      pass
    else:
      print "else"
    
    for x in range(10):
      pass
    print "else"
    

    elseforwhile 一起使用的唯一原因是,如果循环正常终止,则在循环之后执行某些操作,这意味着没有明确的break

    经过深思熟虑,我终于想出了一个可能有用的案例:

    def commit_changes(directory):
        for file in directory:
            if file_is_modified(file):
                break
        else:
            # No changes
            return False
    
        # Something has been changed
        send_directory_to_server()
        return True
    

    【讨论】:

    • 尽管如此,最后一个用例似乎不是一个优雅的解决方案,即使对于这种情况也是如此。
    • 我完全同意,坦率地说我从未使用过这种结构。
    • 但现在,我预测,您可能会看到到处使用它的机会。
    • 我可能会写:“如果文件被修改(文件):send_directory_to_server(); 返回 True”并删除 else: 子句..
    • @John:对于 for-else 构造,你有更好的例子吗? :)
    【解决方案5】:

    也许最好的答案来自官方 Python 教程:

    break and continue Statements, and else Clauses on Loops:

    循环语句可能有一个 else 条款;它在循环时执行 通过用尽而终止 list (with for) 或当条件 变为假(与while),但不是 当循环被中断终止时 声明

    【讨论】:

      【解决方案6】:

      我被介绍给一个很棒的习惯用法,您可以在其中使用带有迭代器的 for/break/else 方案来节省时间和 LOC。手头的示例是为不完全合格的路径搜索候选者。如果您想查看原始上下文,请参阅the original question

      def match(path, actual):
          path = path.strip('/').split('/')
          actual = iter(actual.strip('/').split('/'))
          for pathitem in path:
              for item in actual:
                  if pathitem == item:
                      break
              else:
                  return False
          return True
      

      是什么让for/else 的使用如此出色,因为它可以避免混淆一个令人困惑的布尔值。没有else,但希望达到同样的短路量,可以这样写:

      def match(path, actual):
          path = path.strip('/').split('/')
          actual = iter(actual.strip('/').split('/'))
          failed = True
          for pathitem in path:
              failed = True
              for item in actual:
                  if pathitem == item:
                      failed = False
                      break
              if failed:
                  break
          return not failed
      

      我认为else的使用让它更优雅更明显。

      【讨论】:

        【解决方案7】:

        循环的else 子句的一个用例是打破嵌套循环:

        while True:
            for item in iterable:
                if condition:
                    break
                suite
            else:
                continue
            break
        

        避免重复条件:

        while not condition:
            for item in iterable:
                if condition:
                    break
                suite
        

        【讨论】:

          【解决方案8】:

          给你:

          a = ('y','a','y')
          for x in a:
            print x,
          else:
            print '!'
          

          这是为了守车。

          编辑:

          # What happens if we add the ! to a list?
          
          def side_effect(your_list):
            your_list.extend('!')
            for x in your_list:
              print x,
          
          claimant = ['A',' ','g','u','r','u']
          side_effect(claimant)
          print claimant[-1]
          
          # oh no, claimant now ends with a '!'
          

          编辑:

          a = (("this","is"),("a","contrived","example"),("of","the","caboose","idiom"))
          for b in a:
            for c in b:
              print c,
              if "is" == c:
                break
            else:
              print
          

          【讨论】:

          • 不太高兴!是什么阻止您输入“!”在列表中,还是使用 print '!' 而不使用 else?
          • 如果您使用“打印!”没有其他,那么你会得到'!即使 for 循环有中断,在出现中断的情况下狂喜也是不礼貌的。
          • 另外,由于元组是不可变的,添加一个'!'到元组的末尾需要复制整个内容。
          • Ferdinand 的例子比我的要好,因为它更能控制流量。
          猜你喜欢
          • 2017-08-17
          • 2010-12-26
          • 1970-01-01
          • 1970-01-01
          • 2018-06-06
          • 2017-08-02
          • 2019-12-04
          • 1970-01-01
          • 2014-03-21
          相关资源
          最近更新 更多