ネストされた/複数のリストの内包表記またはジェネレータ式のユースケース。いつエレガントになりますか?
-
19-08-2019 - |
質問
この種のものが時々見られます:
(k for k in (j for j in (i for i in xrange(10))))
今、これは本当に私の脳を曲げます、そして、私はむしろそれがこのように提示されなかったと思います。
ネストされたループである場合よりもエレガントで読みやすいこれらのネストされた式を使用した使用例や例はありますか?
編集:これを簡素化する方法の例に感謝します。それは実際に私が求めたものではなく、エレガントなときがあったのではないかと思っていました。
解決
PEP 202 をチェックします。これは、リスト内包構文が導入された場所です言語。
例を理解するために、グイド自身からの簡単なルールがあります:
- フォーム[... for x ... for y ...]は、最後のインデックスでネストします ネストされたforループのように、最速で変化します。
あなたの質問に答えるのに役立つPEP 202からも:
Rationale List comprehensions provide a more concise way to create lists in situations where map() and filter() and/or nested loops would currently be used.
そのような状況があった場合、よりエレガントであることがわかります。私見ですが、for
ループは視覚的に簡単に解析されるため、複数のネストされたリストの内包表記は、ネストされたforループよりもコード内で明確でない場合があります。
他のヒント
1行で複雑すぎることが心配な場合は、分割することができます:
(k for k in
(j for j in
(i for i in xrange(10))))
Pythonでは、行の継続が少し奇妙に見えることが常にありますが、これにより、それぞれがループしているものを簡単に確認できます。余分な割り当て/ルックアップは何かを壊したり壊したりしないので、次のように書くこともできます:
gen1 = (i for i in xrange(10))
gen2 = (j for j in gen1)
gen3 = (k for k in gen2)
実際には、理解度を2段以上入れ子にしたことはないと思いますが、その時点ではまだ理解しやすいものでした。
あなたの例の場合、おそらく次のように書きます:
foos = (i for i in xrange(10))
bars = (j for j in foos)
bazs = (k for k in bars)
よりわかりやすい名前を付けると、これはおそらく非常に明確になると思います。また、測定可能なパフォーマンスの違いがあるとは想像できません。
おそらく次のような表現を考えているのでしょう:
(x for x in xs for xs in ys for ys in lst)
-実際、それは有効ではありません。物事を他の順序に並べる必要があります:
(x for ys in lst for xs in ys for x in xs)
リストをフラット化する簡単な方法としてそれを書くかもしれませんが、一般的にはあなたが書いていると思います:lessを入力することで節約できる時間は、ジェネレーター式を正しくするために費やす余分な時間と通常バランスが取れています。
これらはジェネレータ式であるため、それぞれを独自の名前にバインドして、パフォーマンスを変更せずに読みやすくすることができます。ネストされたループに変更すると、パフォーマンスが低下する可能性があります。
irange = (i for i in xrange(10))
jrange = (j for j in irange)
krange = (k for k in jrange)
実際にどちらを選択するかは問題ではありません。複数行の例は一般的に読みやすいと思います。
注意:優雅さは部分的に好みの問題です。
リストの内包表記は、対応する展開されたforループよりも明確ではありません。 forループは、リストの内包表記よりも強力です。なぜそれらを使用するのですか?
リストの内包表記とは簡潔です-単一の行で何かを行うことができます。
リスト内包表記を使用するのは、特定のリストが必要な場合で、そのリストはその場で簡単に作成できます。また、中間オブジェクトが必要ない、または必要ない場合です。これは、以下のように、現在のスコープ内のいくつかのオブジェクトを関数にフィードできる単一のオブジェクトにパッケージ化する必要がある場合に発生する可能性があります。
list1 = ['foo', 'bar']
list2 = ['-ness', '-ity']
return filterRealWords([str1+str2 for str1 in list1 for str2 in list2])
このコードは拡張バージョンとほぼ同じくらい読みやすいですが、はるかに短いです。現在のスコープで一度だけ使用されるオブジェクトの作成/命名を回避します。これは間違いなくよりエレガントです。
式:
(k for k in (j for j in (i for i in xrange(10))))
は次と同等です:
(i for i in xrange(10))
それはほとんど同じです:
xrange(10)
最後のバリアントは最初のバリアントよりもエレガントです。
次のようなコードがあるような状況では、便利でエレガントになります。
output = []
for item in list:
if item >= 1:
new = item += 1
output.append(new)
次のようにワンライナーにすることができます:
output = [item += 1 for item in list if item >= 1]