これはむしろ逆です 使用できるPythonジェネレータ関数用? :pythonジェネレータ、ジェネレータ式、およびitertools
モジュールは、最近のpython)のお気に入りの機能の一部です。これらは、大量のデータに対して実行する操作のチェーンを設定するときに特に役立ちます-私はよく使用しますDSVファイルを処理するときにそれら。
それで、いつジェネレーター、またはジェネレーター式、またはitertools
関数を使用するのがいいタイミングではありません?
Zip()
よりitertools.izip()
を優先する場合range()
over xrange()
、または[x for x in foo]
以上(x for x in foo)
?明らかに、通常はリストを作成するか、非ジェネレーターループでリストを反復することにより、最終的にジェネレーターを実際のデータに「解決」する必要があります。時には、長さを知る必要があるだけです。これは私が求めていることではありません。
ジェネレータを使用して、暫定データ用の新しいリストをメモリに割り当てないようにします。これは、特に大きなデータセットの場合に意味があります。小さなデータセットにも意味がありますか?顕著なメモリ/ CPUトレードオフはありますか?
リストの理解のパフォーマンスとmap()およびfilter() の目を見張るような議論に照らして、誰かがこれについてプロファイリングを行っている場合は特に興味があります。 ( 代替リンク )
次の場合にジェネレータの代わりにリストを使用します:
1)データにmultiple回アクセスする必要があります(つまり、結果を再計算する代わりにキャッシュします):
for i in outer: # used once, okay to be a generator or return a list
for j in inner: # used multiple times, reusing a list is better
...
2)ランダムアクセス(または順方向以外のアクセス)が必要です:
for i in reversed(data): ... # generators aren't reversible
s[i], s[j] = s[j], s[i] # generators aren't indexable
3)文字列をjoinする必要があります(データを2回パスする必要があります):
s = ''.join(data) # lists are faster than generators in this use case
4)PyPyを使用しているため、通常の関数呼び出しやリスト操作で生成コードを最適化できない場合があります。
一般に、len()、reversed()などのリスト操作が必要な場合は、ジェネレータを使用しないでください。
また、遅延評価が不要な場合もあります(たとえば、すべての計算を事前に実行してリソースを解放できるようにするなど)。その場合は、リスト式の方が適している場合があります。
プロファイル、プロファイル、プロファイル。
コードのプロファイリングは、実行していることが何らかの影響を与えるかどうかを知る唯一の方法です。
Xrange、ジェネレータなどのほとんどの使用は、静的なサイズ、小さなデータセットを超えています。大規模なデータセットを取得したときにのみ、本当に違いが生まれます。 range()対xrange()は、ほとんどの場合、コードを少し見苦しく見せるだけの問題であり、何も失うことなく、おそらく何かを得ることです。
プロファイル、プロファイル、プロファイル。
「これは特に大規模なデータセットに意味があります」と言ったように、これはあなたの質問に答えると思います。
パフォーマンス面で壁にぶつからない場合でも、リストと標準機能を使用できます。次に、パフォーマンスの問題が発生したときに切り替えます。
ただし、コメントの@ u0b34a0f6aeで言及されているように、最初にジェネレータを使用すると、より大きなデータセットに簡単にスケーリングできます。
パフォーマンスに関して:psycoを使用する場合、リストはジェネレーターよりもかなり高速になる可能性があります。以下の例では、psyco.full()を使用すると、リストがほぼ50%高速になります。
import psyco
import time
import cStringIO
def time_func(func):
"""The amount of time it requires func to run"""
start = time.clock()
func()
return time.clock() - start
def fizzbuzz(num):
"""That algorithm we all know and love"""
if not num % 3 and not num % 5:
return "%d fizz buzz" % num
Elif not num % 3:
return "%d fizz" % num
Elif not num % 5:
return "%d buzz" % num
return None
def with_list(num):
"""Try getting fizzbuzz with a list comprehension and range"""
out = cStringIO.StringIO()
for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
print >> out, fibby
return out.getvalue()
def with_genx(num):
"""Try getting fizzbuzz with generator expression and xrange"""
out = cStringIO.StringIO()
for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
print >> out, fibby
return out.getvalue()
def main():
"""
Test speed of generator expressions versus list comprehensions,
with and without psyco.
"""
#our variables
nums = [10000, 100000]
funcs = [with_list, with_genx]
# try without psyco 1st
print "without psyco"
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
# now with psyco
print "with psyco"
psyco.full()
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
if __name__ == "__main__":
main()
結果:
without psyco
number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds
number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds
with psyco
number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds
number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds
後で何かのために値を保持する必要があり、セットのサイズが大きすぎない場合は、リスト内包表記を優先する必要があります。
たとえば、プログラムで後で数回ループするリストを作成しているとします。
ジェネレータは、反復(ループ)の代わりに、リスト内包表記をデータ構造の初期化のタイプとして、ある程度考えることができます。データ構造を保持したい場合は、リスト内包表記を使用してください。
パフォーマンスに関する限り、ジェネレーターでリストを使用することは考えられません。
ジェネレーターがあなたがやろうとしていることを妨げるような状況を私は見たことがない。ただし、ジェネレータを使用しても、それを使用しないだけでは役に立たない場合がたくさんあります。
例えば:
sorted(xrange(5))
次のものに対する改善はありません。
sorted(range(5))