Pythonで[]なしのリスト内包


85

リストへの参加:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join 反復可能である必要があります。

どうやら、joinの引数は[ str(_) for _ in xrange(10) ]であり、それはリスト内包です。

これを見てください:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

さて、joinの引数はただstr(_) for _ in xrange(10)、いいえ[]ですが、結果は同じです。

どうして?んstr(_) for _ in xrange(10)リストまたは反復可能なのも作りますか?


1
それjoinはおそらくCで書かれているので、リスト内包表記よりもはるかに高速に実行されると思います...テスト時間!
Joel Cornett 2012年

どうやら、私はあなたの質問を完全に間違って読んだ。それは...私のために発電機を返却しているようだ
ジョエル・コルネット

18
注:_特別な意味はありません。通常の変数名です。これは使い捨ての名前としてよく使用されますが、そうではありません(変数を使用しています)。私はそれをコードで使用することを避けます(少なくともこの方法で)。
rplnt 2012年

回答:


67
>>>''.join( str(_) for _ in xrange(10) )

これはジェネレータ式と呼ばれ、PEP289で説明されています。

ジェネレータ式とリスト内包表記の主な違いは、前者はメモリ内にリストを作成しないことです。

式を記述する3番目の方法があることに注意してください。

''.join(map(str, xrange(10)))

1
私が知っているように、ジェネレーターは、のようなタプルのような式で生成できます( str(_) for _ in xrange(10) )。しかし、なぜ、で省略()できるのか、joinつまり、コードは `'' .join((str(_)for _ in xrange(10)))のようにする必要があるのではないかと混乱しました。
アルコット2012年

1
@Alcottタプルについての私の理解は、タプルは実際には括弧ではなくコンマで区切られた式のリストによって定義されているということです。括弧は、割り当て内の値を視覚的にグループ化するため、またはタプルが関数呼び出しなどの他のコンマ区切りリストに入る場合に実際に値をグループ化するためにのみ存在します。これは多くの場合、のようなコードを実行することで示されますtup = 1, 2, 3; print(tup)。そのことを念頭に置いて、for式の一部として使用するとジェネレーターが作成され、括弧は誤って記述されたループと区別するためにあります。
Eric EdLohmar19年

132

他の回答者は、ジェネレータ式(リスト内包表記に似ていますが、周囲の角括弧がない表記)を発見したと答えたのは正しかったです。

一般に、genexps(愛情を込めて知られている)は、リスト内包表記よりもメモリ効率が高く、高速です。

ただし、その場合''.join()、リスト内包表記は高速であり、メモリ効率も高くなります。その理由は、joinはデータに対して2回のパスを行う必要があるため、実際には実際のリストが必要になるためです。あなたがそれを与えるならば、それはすぐにその仕事を始めることができます。代わりにgenexpを指定すると、genexpを実行してメモリ内に新しいリストを作成するまで、作業を開始できません。

~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop

itertools.imapmapを比較しても、同じ結果が得られます

~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop

4
@ lazyr2番目のタイミングはあまりにも多くの仕事をしています。genexpをlistcompにラップしないでください。genexpを直接使用してください。あなたが奇妙なタイミングを得たのも不思議ではありません。
レイモンドヘッティンガー2012年

11
''.join()文字列を作成するためにイテレータを2回通過する必要がある理由を説明できますか?
ovgolovin 2012年

27
@ovgolovin最初のパスは、連結された文字列に正しい量のメモリを割り当てることができるように文字列の長さを合計することであり、2番目のパスは個々の文字列を割り当てられたスペースにコピーすることだと思います。
Lauritz V. Thaulow 2012年

20
@lazyrその推測は正しいです。それはまさにstr.joinが行うことです:
Raymond Hettinger

4
SOで特定の回答を「お気に入り」にする機能を本当に見逃すことがあります。
エア

5

2番目の例では、リスト内包ではなくジェネレータ式を使用しています。違いは、リスト内包表記を使用すると、リストが完全に作成されてに渡されること.join()です。ジェネレータ式を使用すると、アイテムは1つずつ生成され、によって消費され.join()ます。後者はより少ないメモリを使用し、一般的に高速です。

たまたま、リストコンストラクターは、ジェネレーター式を含むすべての反復可能オブジェクトを喜んで消費します。そう:

[str(n) for n in xrange(10)]

次の場合の単なる「シンタックスシュガー」です。

list(str(n) for n in xrange(10))

言い換えれば、リスト内包表記は、リストに変換される単なるジェネレータ式です。


2
それらは内部で同等であると確信していますか?Timeitによると:: [str(x) for x in xrange(1000)]262 usec、list(str(x) for x in xrange(1000)):304usec 。
Lauritz V. Thaulow 2012年

2
@lazyrその通りです。リスト内包はより高速です。そしてこれが、Python2.xでリスト内包表記がリークする理由です。これはGVRが書いたものです: ""これはリスト内包表記の元の実装の成果物でした。それは何年もの間Pythonの「汚い小さな秘密」の1つでした。それは疑いの余地なく、高速リストの内包表記を作るために意図的な妥協案としてスタートし、それは初心者によくある落とし穴はありませんでしたが、時折、それは間違いなく刺さ人「。python-history.blogspot.com/2010/06/...
ovgolovin

3
@ovgolovin listcompが高速である理由は、joinが作業を開始する前にリストを作成する必要があるためです。あなたが言及する「リーク」は速度の問題ではありません-それは単にループ誘導変数がlistcompの外に公開されていることを意味します。
Raymond Hettinger 2012年

1
@RaymondHettingerでは、これらの単語は「リスト内包表記を目がくらむほど速くするための意図的な妥協として始まった」とはどういう意味ですか?私が理解したように、それらの漏れと速度の問題との関係があります。GVRはまた、次のように書いています。「ジェネレータ式の場合、これはできませんでした。ジェネレータ式は、別の実行フレームを必要とするジェネレータを使用して実装されます。したがって、ジェネレータ式(特に短いシーケンスで反復する場合)は、リスト内包表記よりも効率が低くなります。 「」
ovgolovin 2012年

4
@ovgolovin listcomp実装の詳細から、str.joinがそのように動作する理由に誤った飛躍を遂げました。str.joinコードの最初の行の1つは、seq = PySequence_Fast(orig, "");str.join()を呼び出すときに、イテレータがリストやタプルよりも実行速度が遅い唯一の理由です。さらに話し合いたい場合は、チャットを開始してください(私は、PEP 289の作成者であり、LIST_APPENDオペコードの作成者であり、list()コンストラクターを最適化した人なので、いくつかあります。問題に精通している)。
Raymond Hettinger 2012年


4

角かっこではなく親にある場合、技術的にはジェネレータ式です。ジェネレータ式は、Python2.4で最初に導入されました。

http://wiki.python.org/moin/Generators

結合後の部分( str(_) for _ in xrange(10) )は、それ自体がジェネレータ式です。あなたは次のようなことをすることができます:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

そしてそれはあなたが上記の2番目のケースで書いたのとまったく同じことを意味します。

ジェネレーターにはいくつかの非常に興味深いプロパティがありますが、特に、リストが不要なときにリスト全体を割り当てることはありません。代わりに、joinのような関数は、ジェネレータ式からアイテムを一度に1つずつ「ポンプ」し、小さな中間部分で作業を行います。

あなたの特定の例では、リストとジェネレーターのパフォーマンスはおそらくそれほど異なっていませんが、一般的に、ジェネレーターが完全なリストより遅くなることは非常にまれであるため、可能な限りジェネレーター式(およびジェネレーター関数)を使用することを好みますマテリアライゼーション。


1

これはリスト内包表記ではなく、ジェネレーターです。ジェネレーターも反復可能ですが、最初にリスト全体を作成してから結合するのではなく、xrangeの各値を1つずつ渡すため、はるかに効率的です。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.