我几乎没有注意到一个在 for 循环中使用 else 的 python 程序。
我最近用它在退出时根据循环变量条件执行一个动作;因为它在范围内。
在 for 循环中使用 else 的 Pythonic 方式是什么?有什么值得注意的用例吗?
而且,是的。我不喜欢使用 break 语句。我宁愿将循环条件设置为复杂。如果我不喜欢使用 break 语句,我能否从中获得任何好处。
值得注意的是,自语言诞生以来,for 循环就有一个 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 ( 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)
如果你有一个 for 循环,你实际上没有任何条件语句。因此,如果您想中止,那么 break 是您的选择,然后可以完美地处理您不满意的情况。
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 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!
[这与带有 a 的循环一样有效break
,因为该any()
函数是短路的,只运行生成器表达式直到它 yield 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 是可能的除数值,则将不起作用...
如果不使用break
,块对and语句else
没有好处。下面两个例子是等价的:for
while
for x in range(10):
pass
else:
print "else"
for x in range(10):
pass
print "else"
else
使用with for
or的唯一原因while
是如果循环正常终止,则在循环之后执行某些操作,这意味着没有明确的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
也许最好的答案来自官方 Python 教程:
break 和 continue 语句,以及循环上的 else 子句:
循环语句可能有一个 else 子句;它在循环因列表用尽而终止时(使用 for)或条件变为假(使用 while)时执行,但在循环由 break 语句终止时不执行
有人向我介绍了一个很棒的习惯用法,您可以在其中使用带有迭代器的 //for
方案来节省时间和 LOC。手头的示例是为不完全合格的路径搜索候选者。如果您想查看原始上下文,请查看原始问题。break
else
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
使它更优雅,更明显。
循环子句的一个用例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
干得好:
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