使用列表推导只是副作用是Pythonic吗?


108

考虑一下我需要它带来副作用的函数,而不是返回值(例如打印到屏幕,更新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)

哪个更好?为什么?


6
这是临界点,但您可能会得到比支持更多的反对。我要坐这个:^)
jcomeau_ictx

6
这是一个简单的选择。可读性很重要-第二种方式。如果您无法在屏幕上容纳2条额外的行,请使用更大的显示器:)
John La Rooy

1
列表理解是非Python的,因为它违反了“显式优于隐式”的概念,您将循环隐藏在其他构造中。
Fred Foo

3
@larsmans:如果只有GvR意识到,当他首先介绍列表理解时!
史蒂夫·杰索普

2
@ larsmans,Steve Jessop,我认为将列表理解理解为循环是不正确的。它很可能实现为循环,但是这样的构造要点是要以功能性(概念上)并行的方式对聚合数据进行操作。如果语法有问题,for ... in则在两种情况下都会使用它-导致出现这样的问题!
senderle 2011年

Answers:


84

这样做是非常反Python的,任何经验丰富的Pythonista都会为您带来麻烦。中间列表在创建之后就被丢弃了,它可能非常大,因此创建起来很昂贵。


5
那么会有什么更Python化的方式呢?
约阿希姆·绍尔

6
一个没有列出来的列表;即第二种方式的某种变体(我之前知道使用Genex for来摆脱if)。
伊格纳西奥·巴斯克斯

6
@Joachim Sauer:上面的示例2。正确的,明确的,非列表理解的循环。明确的。明确。明显。
S.Lott

31

您不应该使用列表理解,因为正如人们所说的那样,这将建立您不需要的大型临时列表。以下两种方法是等效的:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

与定义consumeitertools手册页:

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如果以前没有进行过函数式编程的人可能不那么直观。
卡特里尔2011年

4
不确定这是否是惯用语。使用显式循环没有任何优势。
Marcin

1
解决方案是consume = collections.deque(maxlen=0).extend
PaulMcG

24

列表推导用于创建列表。并且,除非您实际创建列表,否则不应使用列表推导。

因此,我将选择第二个选项,即遍历列表,然后在条件适用时调用该函数。


6
我还要进一步指出,列表理解内的副作用是不寻常的,意外的,因此是有害的,即使您在完成后使用结果列表也是如此。
Mark Ransom

11

第二更好。

想想需要了解您的代码的人。您可以通过第一个方法轻松获得不良业障:)

您可以使用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)

10
您的lambda最好写成lambda x : x > 3
PaulMcG 2011年

您甚至不需要过滤器。只需在此处将生成器表达式放在括号中:for el in (x for x in y if x > 3):el并且x可以使用相同的名称,但这可能会使人们感到困惑。
全天

3

取决于您的目标。

如果尝试对列表中的每个对象执行某些操作,则应采用第二种方法。

如果您尝试从另一个列表生成列表,则可以使用列表理解。

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


0

你可以做

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

但是不是很漂亮


-1

使用列表理解来产生副作用是丑陋的,非Pythonic的,效率低下的,我不会这样做。我将使用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怎么办?
卡特里尔2011年

7
我认为这种疗法比疾病还糟-itertools.consume更加清洁。
PaulMcG 2011年

@PaulMcG- itertools.consume不再存在,可能是因为使用具有副作用的理解是丑陋的。
全天

1
原来我弄错了,它在stdlib中从来没有作为一种方法存在。它在迭代工具文档配方:docs.python.org/3/library/...
PaulMcG
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.