在for循环中Pythonic使用'else'的方法

17 投票
8 回答
10391 浏览
提问于 2025-04-15 11:04

我几乎没见过哪个Python程序在for循环中使用else。

最近我用它来根据循环变量的条件在退出时执行某个操作,因为它在作用范围内。

在for循环中,使用else的Python风格是什么样的?有没有什么特别的用法?

对了,我不喜欢用break语句。我更愿意把循环条件设置得复杂一些。如果我不想用break语句,那我能从中得到什么好处呢?

值得一提的是,for循环从这个语言诞生的第一版开始就有else这个功能。

8 个回答

7

如果你有一个for循环,但里面没有什么条件判断。那么如果你想要中断这个循环,使用break就是个不错的选择。而else可以很好地处理你不满意的情况。

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

基本上,它简化了任何使用布尔标志的循环,比如这样:

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 divisible by", divisor)
else:
    print(n, "is prime")

而且让你不用再管理这个标志了:

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

注意,当你找到一个除数时,代码有一个自然的执行位置——就在 break 之前。这里唯一的新特性是,当你尝试了所有的除数却没有找到时,有一个地方可以执行代码。

这只有在配合 break 使用时才有帮助。如果你不能使用 break(比如你在寻找最后一个匹配,或者需要同时跟踪多个条件),你仍然需要布尔值。

哦,顺便说一下,这对 while 循环同样适用。

any/all

如果循环的唯一目的是得到一个是或否的答案,可以使用 any()/all() 函数配合生成器或生成器表达式:

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

注意这段代码的优雅!代码和你想表达的意思一模一样!

[这和带有 break 的循环在效率上相似,因为 any() 函数是短路的,只会运行生成器表达式直到它返回 True 为止。]

不过,这样做不会给你实际的除数,因为 any() 总是返回 TrueFalse。当你需要同时获得 (A) 当前找到的值和 (B) “找到”和“未找到”情况的不同代码路径时,带有 else: 的循环是最好的选择。

18

还有什么比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文件的第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)

撰写回答