【问题标题】:Compound conditions in a for loopfor循环中的复合条件
【发布时间】:2017-03-28 08:32:37
【问题描述】:

Python 允许在列表推导中使用“if”条件,例如:

[l for l in lines if l.startswith('example')]

常规的“for”循环中缺少此功能,因此没有:

for line in lines if line.startswith('example'):
    statements

需要评估循环中的条件:

for line in lines:
    if line.startswith('example'):
        statements

或嵌入生成器表达式,如:

for line in [l for l in lines if l.startswith('example')]:
    statements

我的理解正确吗?有没有比我上面列出的更好或更 Pythonic 的方式来实现在 for 循环中添加条件的相同结果?

请注意,选择“行”只是作为示例,任何集合或生成器都可能存在。

【问题讨论】:

  • 您是在问如何在 for 循环中获取复合条件?
  • 但是这些是完全一样的。您从左到右阅读列表推导,它包含与完整 for 循环完全相同的功能。
  • 你能说一下你的问题是什么吗?
  • @leaf:我扩大了一点问题以使其更清楚,如果您需要更多详细信息,请告诉我。
  • @DanielRoseman 不确定我是否理解您的评论。上面的所有示例都应该是等功能的。问题是:为什么 Python“for”循环中没有第二种语法,而它存在于列表推导中?我想这是因为相同的目标可以通过不同的、更 Python 的语法来实现,但我不确定是哪一个。

标签: python for-loop syntax generator list-comprehension


【解决方案1】:

一些不错的想法来自其他答案和 cmets,但我认为 this recent discussion on Python-ideasits continuation 是这个问题的最佳答案。

总结一下:这个想法在过去已经讨论过,考虑到这些好处似乎不足以激发语法更改:

  • 增加的语言复杂性和对学习曲线的影响

  • 所有实现中的技术更改:CPython、Jython、Pypy..

  • 过度使用合成器可能导致的奇怪情况

人们似乎高度重视的一点是避免带来类似 Perl 的复杂性,从而损害可维护性。

This messagethis one 很好地总结了 for 循环中复合 if 语句的可能替代方案(几乎已经出现在此页面中):

# nested if
for l in lines:
    if l.startswith('example'):
        body

# continue, to put an accent on exceptional case
for l in lines:
    if not l.startswith('example'):
        continue
    body

# hacky way of generator expression
# (better than comprehension as does not store a list)
for l in (l for l in lines if l.startswith('example')):
    body()

# and its named version
def gen(lines):
    return (l for l in lines if l.startswith('example'))
for line in gen(lines):
    body

# functional style
for line in filter(lambda l: l.startswith('example'), lines):
    body()

【讨论】:

  • filter 应该相反:filter(lambda l: l.startswith('example'), lines)
【解决方案2】:

也许不是 Pythonic,但你可以filter 行。

for line in filter(lambda l: l.startswith('example'), lines):
    print(line)

当然,如果 lambda 对您造成困扰,或者您想要更复杂的过滤,您也可以定义自己的过滤函数。

def my_filter(line):
    return line.startswith('example') or line.startswith('sample')

for line in filter(my_filter, lines):
    print(line)

我会说在循环中包含条件会更好,因为您在遍历行时不会在内存中维护“过滤”列表。

所以,那就是

for line in file:
    if not my_filter(line):
        continue
    # statements

【讨论】:

  • 很好,谢谢。我对此很认真,但忘记添加了。尽管如此,我仍然认为它不具有仅在循环本身中具有条件的可读性。我不知道我是不是第一个感觉到需要这种语法的人。
  • @Pintun FWIW,不久前刚刚在 Python-ideas 上讨论过这个问题,您可以在其中阅读一些核心 CPython 开发人员的回复:September 2016,然后继续阅读 October 2016。据我所知,这个想法之前也出现过几次。我强烈建议您阅读整个主题。
  • @suspicousdog 谢谢,在那里我找到了我正在寻找的一切:参考之前的讨论,支持和反对更改的技术和美学原因,替代方案的绝佳示例。我发现特别有趣的this 消息,我将使用它作为自我回答的指南。我不知道Python-ideas,很棒的链接!
【解决方案3】:

不是缺少这个功能,除了某些特殊情况,我想不出有什么办法可以做到。 (l for l in lines if l.startswith('example')) 是一个生成器对象,l 变量是该对象的本地变量。 for 只能看到生成器的 __next__ 方法返回的内容。

for 非常不同,因为生成器的结果需要绑定到调用者范围内的变量。你可以写

for line in (line for line in lines if l.startswith('example')):
    foo(line)

安全,因为这两个 line 位于不同的范围内。

此外,生成器不必只返回其局部变量。它可以评估任何表达式。你会怎么做?

for line in (foo(line)+'bar' for line in lines if line.startswith('example')):
    statements

假设你有一个列表列表

for l in (l[:] for l in list_of_lists if l):
    l.append('modified')

不应附加到原始列表中。

【讨论】:

    【解决方案4】:

    有没有比我上面列出的更好或更 Pythonic 的方式来实现在 for 循环中添加条件的相同结果?

    不,没有,也不应该有; 这就是为什么列表推导首先出现在这里的理由。来自corresponding PEP

    列表推导提供了一种更简洁的方式在当前使用map()filter() 和/或嵌套循环的情况下创建列表。

    列表推导构成了嵌套forifs 的替代方案;为什么你想要一个替代方案?

    如果您需要使用 iffor,请将其嵌套在其中,如果您不想这样做,则使用列表推导式。 扁平比嵌套更好 但是 可读性很重要;允许if 存在会导致难以视觉解析的长而难看的行。

    【讨论】:

    • "列表推导构成了嵌套 for、ifs 的替代方案;为什么要替代替代方案?"在这种情况下,它们不是替代方案:理解列表仍然需要迭代一个 for 循环。
    猜你喜欢
    • 2019-02-06
    • 1970-01-01
    • 2013-07-18
    • 2018-01-24
    • 1970-01-01
    • 2016-04-10
    • 2018-07-13
    • 2019-06-04
    相关资源
    最近更新 更多