【问题标题】:Pythonic way to cycle through purely side-effect-based comprehension循环通过纯粹基于副作用的理解的 Pythonic 方式
【发布时间】:2015-11-25 05:50:43
【问题描述】:

在您不关心返回值而是操作完全基于副作用的情况下,执行完整生成器理解的最 Pythonic 方式是什么?

一个例子是根据here 讨论的谓词值拆分列表。很自然地想到写一个生成器理解

split_me = [0, 1, 2, None, 3, '']
a, b = [], []
gen_comp = (a.append(v) if v else b.append(v) for v in split_me)

在这种情况下,我能想出的最佳解决方案是使用any

any(gen_comp)

但是,对于没有看到这种模式的人来说,这并不是很明显。有没有更好的方法来循环通过完整的理解而不将所有返回值保存在内存中?

【问题讨论】:

标签: python generator list-comprehension


【解决方案1】:

在多行写东西并使用if-statements 没有什么非pythonic:

for v in split_me:
    if v:
        a.append(v)
    else:
        b.append(v)

如果你想要一个单行,你可以通过将循环放在一行来做到这一点:

for v in split_me: a.append(v) if v else b.append(v)

如果你想在一个表达式中使用它(这仍然让我觉得你为什么想要它,除非你有一个你想要摆脱它的值)你可以使用列表推导来强制循环:

[x for x in (a.append(v) if v else b.append(v) for v in split_me) if False]

您认为哪种解决方案最能说明您正在做的事情?我会说第一个解决方案。要成为pythonic,您可能应该考虑zen of python,尤其是:

  • 可读性很重要。
  • 如果实现难以解释,那就是个坏主意。

【讨论】:

  • 我对你的单线有点不舒服,因为它使用了一个条件表达式,在两个分支中产生相同的结果(无),然后丢弃结果; OTOH,使用表达式来表示其副作用并不像使用 gen exp 或 list comp 来表示其副作用那么糟糕。另一种单行是 Martijn 的最后一个答案的浓缩版本:`for v in split_me: (a if v else b).append(v),但我仍然希望在两行中看到它。
  • 我想我很清楚 - 你应该对他们(有点)不舒服。
【解决方案2】:

正如其他人所说,不要仅仅为了副作用而使用推导。

这是使用来自itertoolspartition() 配方来完成您实际尝试做的事情的好方法:

try:  # Python 3
    from itertools import filterfalse
except ImportError:  # Python 2
    from itertools import ifilterfalse as filterfalse
    from itertools import ifilter as filter


from itertools import tee


def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # From itertools recipes:
    # https://docs.python.org/3/library/itertools.html#itertools-recipes
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

split_me = [0, 1, 2, None, 3, '']

trueish, falseish = partition(lambda x: x, split_me)

# You can iterate directly over trueish and falseish,
# or you can put them into lists

trueish_list = list(trueish)
falseish_list = list(falseish)

print(trueish_list)
print(falseish_list)

输出:

[0, None, '']
[1, 2, 3]

【讨论】:

    【解决方案3】:

    itertools 有这个消费配方

    def consume(iterator, n):
        "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)
    

    在你的情况下,nNone,所以:

    collections.deque(iterator, maxlen=0)
    

    这很有趣,但也有很多机器可以完成一项简单的任务

    大多数人只会使用for 循环

    【讨论】:

      【解决方案4】:

      您这样做是通过不使用生成器表达式

      只要写一个合适的循环:

      for v in split_me:
          if v:
              a.append(v)
          else:
              b.append(v)
      

      或许:

      for v in split_me:
          target = a if v else b
          target.append(v)
      

      如果您要立即执行生成器无论如何,在此处使用生成器表达式是没有意义的。当您只想将值附加到其他两个列表时,为什么还要生成一个对象和一系列 None 返回值?

      使用显式循环对于未来的代码维护者(包括您)来说更容易理解,而且效率更高。

      【讨论】:

      • 如果他想使用常量内存(例如,如果split_me 是巨大的)那么split_me 应该从生成器中取出。但是没有足够的细节来说明这是否有用。
      猜你喜欢
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      • 1970-01-01
      • 2016-10-28
      • 1970-01-01
      • 2010-09-22
      • 2018-03-18
      • 2018-05-27
      相关资源
      最近更新 更多