以python的方式循环进行纯粹基于副作用的理解



在不关心返回值的情况下,执行完全基于副作用的操作,最python的方法是什么?

一个例子是根据这里讨论的谓词值拆分列表。很自然会想到写一个生成器推导式

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)

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

通过不使用生成器表达式来实现。

写一个合适的循环:

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返回值序列?

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

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循环

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

这里有一个很好的方法来做你实际上想做的事情,使用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]

在多行上写东西并没有什么不符合python的,并且可以使用if -语句:

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]

你认为哪个解决方案最能显示你在做什么?我认为是第一个解。要使用python,您可能应该考虑python的禅意,特别是:

  • 可读性。
  • 如果实现很难解释,这是一个坏主意。

只是抛出另一个原因,为什么使用any()来消耗生成器是一个可怕的想法,您需要记住any()和all()保证会进行短路计算,这意味着如果生成器返回True值,那么all()将提前退出并使生成器未完全消耗。

这是添加一个额外的条件测试/停止条件,A)可能不想要,B)可能离生成器创建的地方很远。

许多标准库函数返回None,所以你可以暂时摆脱all(),直到它突然不做你期望的事情,如果你已经养成了以这种方式使用all()的习惯,你可能会盯着这段代码很长时间才想到。

如果你必须这样做,那么itertools.consume()是我认为唯一合理的方法。

any虽短,但并非通解。对任何生成器都有效的是直接的

for _ in gen_comp: pass

也比一般的any方法更短,更有效,

any(None for _ in gen_comp)

所以for循环是最清晰最好的。它唯一的缺点是不能在表达式中使用。

最新更新