【问题标题】:Is it Pythonic to use list comprehensions for just side effects?将列表推导用于副作用是 Pythonic 吗?
【发布时间】:2011-08-10 20:22:33
【问题描述】:

想想我为它的副作用调用的函数,而不是返回值(如打印到屏幕、更新 GUI、打印到文件等)。

def fun_with_side_effects(x):
    ...side effects...
    return y

现在,是不是 Pythonic 使用列表推导来调用这个函数:

[fun_with_side_effects(x) for x in y if (...conditions...)]

请注意,我不会将列表保存在任何地方

或者我应该这样称呼这个函数:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

哪个更好,为什么?

【问题讨论】:

  • 这是临界点,但你可能会得到更多的反对而不是支持。我要坐下这个:^)
  • 这是一个简单的选择。可读性很重要——用第二种方法。如果您的屏幕上无法容纳 2 条额外的线,请使用更大的显示器 :)
  • @larsmans:如果 GvR 在他一开始引入列表推导时就意识到了这一点!
  • @larsmans,Steve Jessop,我认为将列表理解理解为循环是不正确的。它可以很好地实现为一个循环,但像这样的构造点是以功能和(概念上)并行的方式对聚合数据进行操作。如果语法有问题,那就是for ... in 在这两种情况下都使用了——导致这样的问题!
  • @senderle:不过,我认为这取决于副作用。如果副作用一次只改变一个元素,独立地,那么我认为在命令式语言中使用函数式结构是完全合理的,因为重要的不是循环流程控制,而是每个人的应用程序元素。如果副作用使得顺序很重要,那么“理解”抽象可能开始泄漏。不过,它是否泄漏到足够重要的程度是另一个问题——没有人假装 Python 会进行惰性求值。

标签: python list-comprehension


【解决方案1】:

这样做是非常反 Python 的,任何经验丰富的 Pythonista 都会让你大吃一惊。中间列表在创建后被丢弃,它可能非常非常大,因此创建成本很高。

【讨论】:

    【解决方案2】:

    列表推导用于创建列表。除非您实际上是在创建一个列表,否则您应该不要使用列表推导式。

    所以我会选择第二个选项,只需遍历列表,然后在条件适用时调用函数。

    【讨论】:

    • 我会更进一步,并指出列表解析中的副作用是不寻常的、意想不到的,因此是邪恶的,即使您在完成后使用结果列表也是如此。
    【解决方案3】:

    第二个更好。

    想想需要理解您的代码的人。第一个你可以很容易地得到恶业:)

    您可以使用 filter() 介于两者之间。考虑这个例子:

    y=[1,2,3,4,5,6]
    def func(x):
        print "call with %r"%x
    
    for x in filter(lambda x: x>3, y):
        func(x)
    

    【讨论】:

    • 你甚至不需要过滤器。只需将生成器表达式放在括号中:for el in (x for x in y if x > 3):elx 可以使用相同的名称,但这可能会让人感到困惑。
    • 请注意,这两个xs 在不同的范围内,这是不好的做法。
    【解决方案4】:

    取决于你的目标。

    如果你想对列表中的每个对象做一些操作,应该采用第二种方法。

    如果您尝试从另一个列表生成列表,您可以使用列表推导。

    显式优于隐式。 简单胜于复杂。 (Python 禅)

    【讨论】:

    • 原则上最好的答案。语法遵循规则,因此可以回答“正确”、“错误”、“这样”、“那样”。风格服从于其他考虑,例如常识、优雅、简洁、有效性、效率等。这些取决于一个人的目的,甚至取决于一个人的品味。
    【解决方案5】:

    你可以的

    for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass
    

    但它不是很漂亮。

    【讨论】:

      【解决方案6】:

      对它的副作用使用列表推导是丑陋的、非 Python 的、低效的,我不会这样做。我会改用for 循环,因为for 循环表示一种程序风格,其中副作用很重要。

      但是,如果您绝对坚持使用列表推导来解决它的副作用,您应该改用生成器表达式来避免效率低下。如果您绝对坚持这种风格,请选择以下两种方式之一:

      any(fun_with_side_effects(x) and False for x in y if (...conditions...))
      

      或:

      all(fun_with_side_effects(x) or True for x in y if (...conditions...))
      

      这些是生成器表达式,它们不会生成被丢弃的随机列表。我认为all 形式可能更清晰一些,尽管我认为它们都令人困惑并且不应该使用。

      我认为这很丑陋,我实际上不会在代码中这样做。但如果你坚持以这种方式实现你的循环,我会这样做。

      我倾向于认为列表推导及其同类应该表明尝试使用至少与函数式风格相似的东西。放置带有破坏该假设的副作用的东西将导致人们不得不更仔细地阅读您的代码,我认为这是一件坏事。

      【讨论】:

      • 如果fun_with_side_effects 返回True怎么办?
      • 我认为这种治疗方法比疾病更糟糕——itertools.consume 干净得多。
      • @PaulMcG - itertools.consume 不再存在,可能是因为使用带有副作用的推导很难看。
      • 原来我弄错了,它从来没有作为标准库中的方法存在。它 itertools 文档中的一个配方:docs.python.org/3/library/…
      【解决方案7】:

      您不应该使用 list 推导式,因为正如人们所说,这会构建一个您不需要的大型临时列表。以下两种方法是等价的:

      consume(side_effects(x) for x in xs)
      
      for x in xs:
          side_effects(x)
      

      consume 的定义来自 itertools 手册页:

      def consume(iterator, n=None):
          "Advance the iterator n-steps ahead. If n is none, consume entirely."
          # Use functions that consume iterators at C speed.
          if n is None:
              # feed the entire iterator into a zero-length deque
              collections.deque(iterator, maxlen=0)
          else:
              # advance to the empty slice starting at position n
              next(islice(iterator, n, n), None)
      

      当然,后者更清晰易懂。

      【讨论】:

      • @Paul:我认为应该如此。确实可以,不过如果之前没有做过函数式编程,map 可能不会那么直观。
      • 不确定这是否特别地道。使用显式循环没有任何优势。
      • 解决方案是consume = collections.deque(maxlen=0).extend
      猜你喜欢
      • 1970-01-01
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      • 2015-10-14
      • 1970-01-01
      相关资源
      最近更新 更多