在for循环中Pythonic使用'else'的方法
我几乎没见过哪个Python程序在for循环中使用else。
最近我用它来根据循环变量的条件在退出时执行某个操作,因为它在作用范围内。
在for循环中,使用else的Python风格是什么样的?有没有什么特别的用法?
对了,我不喜欢用break语句。我更愿意把循环条件设置得复杂一些。如果我不想用break语句,那我能从中得到什么好处呢?
值得一提的是,for循环从这个语言诞生的第一版开始就有else这个功能。
8 个回答
如果你有一个for循环,但里面没有什么条件判断。那么如果你想要中断这个循环,使用break就是个不错的选择。而else可以很好地处理你不满意的情况。
for fruit in basket:
if fruit.kind in ['Orange', 'Apple']:
fruit.eat()
break
else:
print 'The basket contains no desirable fruit'
基本上,它简化了任何使用布尔标志的循环,比如这样:
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()
总是返回 True
或 False
。当你需要同时获得 (A) 当前找到的值和 (B) “找到”和“未找到”情况的不同代码路径时,带有 else:
的循环是最好的选择。
还有什么比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)