副作用のためにリスト内包表記を使用するのは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...)]

リストはどこにも保存しないことに注意してください

または、このfuncを次のように呼び出す必要があります。

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

どちらがより良いのですか、そしてなぜですか?


6
これは境界線ですが、おそらくサポートよりも反対になるでしょう。私はこれを座らせます:^)
jcomeau_ictx

6
これは簡単な選択です。読みやすさが重要です-2番目の方法で行います。画面に2行追加できない場合は、モニターを大きくします:)
John La Rooy

1
リストの内包表記は、「明示的なものは暗黙的なものよりも優れている」に違反しているため、Pythonではありません。ループを別の構成で非表示にしています。
Fred Foo

3
@larsmans:GvRだけが最初にリスト内包表記を導入したときにそれを実現した場合のみ!
スティーブジェソップ

2
@larsmans、スティーブ・ジェソップ、リスト内包表記をループとして考えるのは間違っていると思います。ループとして実装することもできますが、このような構成のポイントは、機能的かつ(概念的に)並列に集計データを操作することです。構文に問題がある場合、それfor ... inは両方のケースで使用されます-このような質問につながります!
センダーレ、2011

回答:


84

そうすることは非常に反Pythonicであり、ベテランのPythonistaはあなたに地獄を与えるでしょう。中間リストは作成後に破棄されるため、非常に大きくなる可能性があり、そのため作成にコストがかかる可能性があります。


5
それでは、よりpythonicな方法は何でしょうか?
ヨアヒムザウアー

6
リストを保持しないもの。つまり、2番目の方法の一部のバリアント(for以前にGenexを使用してを取り除くことが知られていますif)。
Ignacio Vazquez-Abrams

6
@Joachim Sauer:上記の例2。適切な、明示的な、リスト非理解ループ。明示的。晴れ。明らかです。
S.Lott、2011

31

リスト内包表記は使用しないでください。人々が言っ​​たように、必要のない大きな一時リストが作成されるからです。次の2つの方法は同等です。

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

定義とconsumeからitertoolsmanページ:

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関数型プログラミングを以前に行ったことがなければ、直感的ではないかもしれません。
Katriel

4
これが特に慣用的であるかどうかはわかりません。明示的なループを使用することに勝る利点はありません。
Marcin 2013

1
解決策はconsume = collections.deque(maxlen=0).extend
PaulMcG

24

リスト内包表記は、リストを作成するためのものです。また、実際にリストを作成しているのでない限り、リスト内包表記を使用しないでください。

だから私は2番目のオプションを得るために、リストを反復して、条件が適用されたときに関数を呼び出します


6
さらに進んで、リスト内包の内部の副作用は異常で予期せぬものであり、結果として得られたリストを使用していても、それが悪であることを述べます。
マークランサム

11

二番目は良いです。

あなたのコードを理解する必要がある人を考えてください。あなたは最初の:)で簡単に悪いカルマを得ることができます

filter()を使用して、2つの中間に移動できます。例を考えてみましょう:

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 x : x > 3ます。
PaulMcG 2011

フィルターすら必要ありません。ここにジェネレータ式を括弧で囲みます:for el in (x for x in y if x > 3):elそして、x同じ名前を持つことができますが、それは人々を混乱させる可能性があります。
18年

3

あなたの目標に依存します。

リスト内の各オブジェクトに対して何らかの操作を実行しようとしている場合は、2番目のアプローチを採用する必要があります。

別のリストからリストを生成しようとしている場合は、リスト内包表記を使用できます。

明示的は暗黙的よりも優れています。シンプルは複雑よりも優れています。(Python Zen)


0

できるよ

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

しかし、それは非常にきれいではありません。


-1

副作用のためにリスト内包表記を使用することは醜く、Python以外の方法で非効率的であり、私はそれをしません。forループは、for副作用が重要な手続き型の信号であるため、代わりにループを使用します。

ただし、副作用のためにリスト内包表記の使用を強く主張する場合は、代わりにジェネレータ式を使用して非効率を回避する必要があります。このスタイルを絶対に主張する場合は、次の2つのいずれかを実行してください。

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_effectsTrueを返すとどうなりますか?
カトリエル

7
私はこの治療法は病気よりも悪いと思います-itertools.consumeははるかにきれいです。
PaulMcG 2011

@PaulMcG- itertools.consumeおそらく、副作用のある内包表記を使用するのは醜いためです。
方位

1
それは私が間違っていたことがわかりました、そしてそれはstdlibのメソッドとして存在したことはありません。これは、ある itertoolsのドキュメント内のレシピ:docs.python.org/3/library/...
PaulMcG
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.