2016-01-16 9 views
5

巨大な文字列からアルファベットを抽出する問題を考えてみましょう。リスト内包は、文字のリストを生成します。実行する弦に結合する。ジェネレータまたはリストの理解?

一つの方法は、メカニズムは明らかである

''.join([c for c in hugestring if c.isalpha()]) 

です。結合メソッドは、リストの長さにアクセスして結合する必要のある文字の数を認識します。行うには

他の方法は、ここで

''.join(c for c in hugestring if c.isalpha()) 

発電機で発電理解した結果です。結合メソッドは、生成元がlen属性を持たないため、結合する文字数を認識しません。したがって、この結合方法はリストの理解方法よりも遅くなるはずです。

しかし、Pythonでテストすると、遅くはないことがわかります。なぜこれはそうですか? ジェネレータで結合がどのように機能するかは誰でも説明できます。

は明確であるために:それは累積合計を追跡することができますので、

sum(j for j in range(100)) 

は、100のいずれかの知識を持っている必要はありません。ジェネレータの次のメソッドを使用して次の要素にアクセスし、累積合計に加算することができます。 しかし、文字列は不変であるため、文字列を結合すると、各繰り返しで新しい文字列が作成されます。だからこれには多くの時間がかかります。

答えて

10

str.join(gen)を呼び出します。genがジェネレータである場合、Pythonはlist(gen)に相当する処理を行い、結果のシーケンスの長さを調べます。

具体的には、あなたlook at the code implementing str.join in CPython場合は、このコールが表示されます:それはすでにリストやタプルではなかった場合

fseq = PySequence_Fast(seq, "can only join an iterable"); 

PySequence_Fastへの呼び出しはリストにseq引数を変換します。

したがって、呼び出しの2つのバージョンはほぼ同じように処理されます。リストの理解では、自分でリストを作成してjoinに渡します。ジェネレータ表現バージョンでは、渡したジェネレータオブジェクトはjoinの先頭にあるlistになり、残りのコードは両方のバージョンで同じように動作します。

+0

したがって、スピード違反通知の違いはまさに情緒的なものでなければなりません。 –

+0

@ Ev.Kounis:質問者は、2つのバージョンが同じスピード(「**遅くない」)であると言いました。これは、「参加」の時間とリストの理解の時間の両方を測定していれば意味があります一緒に。 'join 'だけを測定した場合、ジェネレータ表現バージョンは、ジェネレータ表現のバージョンが遅くなります。なぜなら、結合する文字列を実行する前にジェネレータ全体をリストにダンプする必要があるからです。それは、リストの理解を構築するのと同じくらい多くの時間がかかるでしょう。 – Blckknght

1

join()は、シーケンスの要素をより長く蓄積された文字列に連続して追加することで実装する必要はありません(長いシーケンスでは非常に遅くなります)。同じ結果を出すだけでよいのです。だからjoin()はおそらくちょうどいくつかの内部メモリバッファに文字を追加し、最後にそれから文字列を作成しています。リストの理解の構造は、一方でリストを構築し(hugestringのジェネレータをトラバースすることによって)、最初にjoin()の作業を開始させる必要があります。

また、join()は、各要素が単一の文字であることを知ることができないので(ほとんどの場合、そうではありません)、おそらくリストからジェネレータを取得するだけです。

+2

参照インタープリタのC層コードは、この目的のための完全な(しかしプライベートな) '_PyUnicodeWriter' API(および他の同様の「ビルド文字列小刻みの」ケース)。 Javaの 'StringBuilder'クラスと比較してください。 – ShadowRanger

+1

それは@Blckknightが正しいと思われます。 'list'や' tuple'でない場合は、内部的に入力を 'list'に変換しています。また、 '_PyUnicodeWriter'をまったく使用するのではなく、最終的な値の長さを計算して必要なだけ事前に割り当てるように事前計算パスを実行するように見えます。 – ShadowRanger

1

少なくとも私のマシンでは、おそらく''.joinがメモリ割り当てを最適化することができるため、テストしたケースでリストの解説が高速になります。それはおそらくちょうど(あなたがテストしている状態があまり頻繁に発生した場合、CPythonのは先に時間の長さを知らないために支払う価格は小さくてもよい、など)あなたがテストしている具体例に依存します。

In [18]: s = ''.join(np.random.choice(list(string.printable), 1000000)) 

In [19]: %timeit ''.join(c for c in s if c.isalpha()) 
10 loops, best of 3: 69.1 ms per loop 

In [20]: %timeit ''.join([c for c in s if c.isalpha()]) 
10 loops, best of 3: 61.8 ms per loop 
+1

これは、リストの補完が過大に最適化されている( 'list'を直接生成します。ジェネレータ式は汎用イテレータプロトコルを使用して消費しなければならない値を' yield 'します)。参加する。同じテストを実行しますが、 '' '.join'を 'list'と置き換えてください(二番目のケースでは完全に省略することができます)。ジェネレータ式の周りの 'list'コンストラクタはかなり遅く、この大きな入力に対して' list'に関連するルックアップや関数呼び出しのコストとはまったく関係ありません。 – ShadowRanger

関連する問題