2

yield多くの場合、人々はあらゆる方法で「要素を怠惰に作成するために使用する」と言います。yieldしかし、とそのイテレータを含め、すべてにコストがかかると思います。

効果的な北の目では、それはいい質問だと思います。たとえば、関数を取得したときです。

def list_gen(n):
    if n > MAGIC_NUM:
        return xrange(n)
    else:
        return range(n)

MAGIC_NUMの投与量はどれくらいですか?

UPDATEこの間違いで申し訳ありませんが、私は元々、イテレータのコストとリストのコストを比較することを意味しています。

もう一度更新してください。ケースを画像化してください。条件があるかどうかにかかわらず、メモリが制限されているため、イテレータを作成できません。

ハ、この質問は今もっと面白いです。
もう一度更新するイテレータを作成してyieldコンテキストを保存する方が、リストを作成するよりも少ないのはなぜですか?またはイテレータの費用はいくらですか?(私の侮辱でごめんなさい)何バイトですか?

4

5 に答える 5

4

あなたはいくつかのことを混ぜ合わせています。

def list_gen(n):
    i=0
    while i<n:
        yield i
        i += 1

この関数はジェネレータです。これを呼び出すと、イテレータであるジェネレータオブジェクトが返されます。

イテレータは、を持っているものですnext()。つまり、一度トラバースすることができます。イテレータはiter、を実行するたびにを使用して何かの上に作成されますfor i in something

def list_gen(n):
    return range(n)

def list_gen(n):
    return xrange(n)

これらの関数は通常の関数です。1つはaを返し、もう1つはオブジェクトlistを返しxrangeます。リストとxrangeはどちらも反復可能です。つまり、複数の独立した反復子を作成できます。


質問に戻りましょう。あなたは、オブジェクトを返すかオブジェクトを返すかを尋ねてlistxrangeます。

それは明らかに依存します!それはあなたが結果で何をしたいかによります。

  • どういうわけかそれを変更したい場合は、実際のリストが必要です。range直接使用してください。

  • それだけを繰り返したい場合は、意味的には違いはありません。xrangeオブジェクトとlistによって返されるbyの両方rangeが、同じシーケンスを繰り返すイテレーターを生成します。

    ただし、を使用するxrangeと、リスト全体がメモリに作成されることはありません。list単純な反復だけを実行したいのに、なぜ本格的なオブジェクトをメモリに作成するのでしょうか。forループが必要なときはいつでも、一時的に大きなメモリバッファを割り当てる必要はありませんよね?

したがってxrange、発信者はいつでもlistそれを利用できるため、に固執するのは安全です。


ベンチマークで確認しましょう。によって構築されたリストよりもxrangeを反復処理する方が速いかどうかを知りたいです(もちろん、呼び出しrangeのコストを含みます)。range

コード:

import timeit

ns = [1,2,3, 5, 10, 50, 100]
print 'n', '\t', 'range', '\t', 'xrange'
for n in ns:
    t1 = timeit.timeit("for i in range({}): pass".format(n))
    t2 = timeit.timeit("for i in xrange({}): pass".format(n))
    print n, '\t', t1, '\t', t2

結果:

n       range           xrange
1       0.566222990493  0.418698436395
2       0.594136874362  0.477882061758
3       0.630704800817  0.488603362929
5       0.725149288913  0.540597548519
10      0.90297752809   0.687031507818
50      2.44493085566   1.89102105759
100     4.31189321914   3.33713522433
于 2012-11-29T09:46:40.967 に答える
3

これは、生成するイテレータの長さとは関係ありませんが、後でそれをどのように使用する必要があるかとは関係ありません。一度だけ使用する必要がある場合は、必ずyieldを使用する必要があります。複数回使用する場合は、yieldをスキップして、通常のリストを取得できます。イールドを使用して取得するジェネレーターは、1回しか反復できないことに注意してください。

于 2012-11-29T09:30:21.243 に答える
3

あなたの質問とそのタイトルはまだ少し混同されていますが、私は私が理解している方法でそれに答えようとします。

の結果のみを反復処理する場合は、範囲が短い場合も長い場合も(x)range()xrange()(特別なオブジェクト)がrange()(リスト)よりも優れています。

$ python -m timeit 'a=range(3)' 'for i in a: pass'
1000000 loops, best of 3: 0.608 usec per loop
$ python -m timeit 'a=xrange(3)' 'for i in a: pass'
1000000 loops, best of 3: 0.466 usec per loop

$ python -m timeit 'a=xrange(30000)' 'for i in a: pass'
1000 loops, best of 3: 1.01 msec per loop
$ python -m timeit 'a=range(30000)' 'for i in a: pass'
1000 loops, best of 3: 1.49 msec per loop

したがって、常に使用することをお勧めしますxrange()


一般的なケースを見ると、少し異なる場合があります。「事前生成」値/オブジェクトを比較し、それらをリストに格納し、生成直後に消費して後で処理します。

def gen(num):
    import random
    i = 0
    while i < num:
        value = random.random()
        yield value
        i += 1

def process(value): pass

def test1(num):
    data = list(gen(num))
    for i in data: process(num)

def test2(num):
    for i in gen(num): process(num)

ここでは、生産と消費がどのように相互作用できるか、およびオーバーヘッドがどれほど大きいかによって異なります。

それらを独立して動作させたい場合は、スレッド化を使用して「両方を同時に」実行できます。

def list_eater(l):
    while l:
        yield l.pop(0)
def test3(num):
    data = []
    def producer():
        for i in gen(num): data.append(i)
    import threading
    consumerthread = threading.Thread(target=producer)
    consumerthread.start()
    while data or consumerthread.isAlive():
        for item in list_eater(data): process(item)
        # Optimizeable. Does idle waiting; a threading.Condition might be quite useful here...

生産を実行し、すべてのアイテムを消費します。これらは、生産または消費する必要がある限り、ここにあります。

于 2012-11-29T10:00:00.663 に答える
2

またはジェネレーターの使用yieldは、リストのサイズとはほとんど関係ありません。次に例を示します。

  • リスト全体を処理する必要がなく、すぐに壊れてしまう可能性がある場合は、ジェネレーターを使用する方が効率的です。
  • 無限のサイズのストリームをシミュレートします。たとえば、素数ジェネレーターです。

ただし、組み込みシステムなどのメモリが限られていて、リスト全体を一度に作成できない場合は、ジェネレータを使用する必要があります。

コストに関しては、ジェネレーターを使用するための追加コストがあります。ジェネレーターが呼び出されるたびにジェネレーターへの呼び出しを評価するコストを数えると、リストを使用するとより多くのメモリが必要になるため、ジェネレーターがリストよりも優れています。これには、メモリとパフォーマンスの間のトレードオフが含まれるため、ジェネレータを使用するかどうかは、ニーズと状況によって異なります。

于 2012-11-29T09:38:38.800 に答える
2

yieldとの両方を使用することはできませんのでご注意くださいreturn。関数は、ジェネレーター関数または通常の関数のいずれかですが、両方にすることはできません。

通常yield、中間リストを作成する必要はありませんが、代わりに要素を1つずつ生成します。これは、たとえば、再帰的に木を歩いている場合に役立ちます。例については、次のリンクを参照してください:http: //code.activestate.com/recipes/105873-walk-a-directory-tree-using-a-generator/

ジェネレーターのもう1つの使用法は、多数の要素を返したいが、ユーザーはおそらく最初のいくつかの要素のみに関心がある場合です(検索結果など)。

中間リストを回避するとメモリが節約されますが、呼び出し元が結果からリストを作成する必要がない場合に限ります。一般に、利点は、ジェネレーター関数をより簡潔にコーディングできることです。

于 2012-11-29T09:40:28.333 に答える