238

私は現在Pythonクックブックを読んでおり、現在ジェネレーターを検討しています。頭が回らなくて困っています。

私は Java のバックグラウンドを持っているので、同等の Java はありますか? この本では「プロデューサー/コンシューマー」について語られていましたが、それを聞くとスレッド化について思い浮かびます。

ジェネレーターとは何ですか?なぜそれを使用するのですか? 明らかに、本を引用することなく(本から直接まともで単純な答えを見つけることができない限り)。あなたが寛大に感じているなら、おそらく例を挙げてください!

4

13 に答える 13

431

注: この投稿では、Python 3.x 構文を想定しています。

ジェネレーターは、 を呼び出すことができるオブジェクトを返す単純な関数であり、呼び出しnextごとに何らかの値を返し、StopIteration例外を発生させて、すべての値が生成されたことを通知します。このようなオブジェクトはiteratorと呼ばれます。

returnJava と同様に、通常の関数は を使用して単一の値を返します。ただし、Python には、 と呼ばれる代替手段がありyieldます。関数内の任意の場所で使用yieldすると、ジェネレーターになります。次のコードを確認してください。

>>> def myGen(n):
...     yield n
...     yield n + 1
... 
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

ご覧のとおり、はとmyGen(n)を生成する関数です。すべての値が生成されるまで、を呼び出すたびに単一の値が生成されます。バックグラウンドで呼び出しをループするため、次のようになります。nn + 1nextfornext

>>> for n in myGen(6):
...     print(n)
... 
6
7

同様に、特定の一般的なタイプのジェネレーターを簡潔に記述する手段を提供するジェネレーター式があります。

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

ジェネレータ式はリスト内包表記によく似ていることに注意してください。

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

ジェネレーター オブジェクトは 1 回生成されますが、そのコードは一度にすべて実行されるわけではないことに注意してください。next実際にコード (の一部) を実行するための呼び出しのみ。ジェネレーター内のコードの実行はyield、値を返すステートメントに到達すると停止します。next次に then を呼び出すと、最後のyield. これは通常の関数との根本的な違いです。これらの関数は常に「先頭」で実行を開始し、値を返すとその状態を破棄します。

この件については、他にも言いたいことがあります。たとえば、sendデータをジェネレーターに戻すことができます (参照)。しかし、それは、ジェネレーターの基本的な概念を理解するまで調べないことをお勧めします。

なぜジェネレーターを使用するのですか? 正当な理由がいくつかあります。

  • 特定の概念は、ジェネレーターを使用してより簡潔に記述できます。
  • 値のリストを返す関数を作成する代わりに、オンザフライで値を生成するジェネレーターを作成できます。これは、リストを構築する必要がないことを意味します。つまり、結果として得られるコードのメモリ効率が向上します。このようにして、単純に大きすぎてメモリに収まらないデータ ストリームを記述することさえできます。
  • ジェネレーターを使用すると、無限のストリームを自然に記述することができます。たとえば、フィボナッチ数を考えてみましょう:

    >>> def fib():
    ...     a, b = 0, 1
    ...     while True:
    ...         yield a
    ...         a, b = b, a + b
    ... 
    >>> import itertools
    >>> list(itertools.islice(fib(), 10))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    このコードはitertools.islice、無限ストリームから有限数の要素を取得するために使用します。モジュール内の関数をよく確認することをお勧めします。これらの関数は、itertools高度なジェネレータを非常に簡単に作成するための不可欠なツールであるためです。


   Python <=2.6 について:上記の例のは、指定されたオブジェクトnextのメソッドを呼び出す関数です。Python <=2.6 では、の代わりに__next__、わずかに異なる手法を使用します。Python 2.7 には呼び出しがあるため、2.7 では以下を使用する必要はありません。o.next()next(o)next().next

>>> g = (n for n in range(3, 5))
>>> g.next()
3
于 2009-11-18T13:54:07.527 に答える
52

ジェネレーターは事実上、終了する前に (データ) を返す関数ですが、その時点で一時停止し、その時点で関数を再開できます。

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

等々。ジェネレーターの (または 1 つの) 利点は、一度に 1 つのデータを処理するため、大量のデータを処理できることです。リストでは、過度のメモリ要件が問題になる可能性があります。ジェネレーターは、リストと同様に反復可能であるため、同じ方法で使用できます。

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at 
a 
time

ジェネレーターは、無限を扱う別の方法を提供することに注意してください。たとえば、

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())    
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000   

ジェネレーターは無限ループをカプセル化しますが、要求するたびにそれぞれの答えしか得られないため、これは問題ではありません。

于 2009-11-18T14:24:02.607 に答える
31

まず第一に、ジェネレータという用語は元々Pythonでいくらか不明確に定義されていたため、多くの混乱を招きました。あなたはおそらくイテレータイテレータを意味します(ここを参照)。次に、Pythonには、ジェネレーター関数(ジェネレーターオブジェクトを返す)、ジェネレーターオブジェクト(イテレーター)、ジェネレーター式(ジェネレーターオブジェクトに評価される)もあります。

ジェネレーターの用語集のエントリによると、正式な用語は、ジェネレーターが「ジェネレーター機能」の略であるということのようです。過去には、ドキュメントで一貫性のない用語が定義されていましたが、幸い、これは修正されました。

それでも正確であり、それ以上の指定なしに「ジェネレータ」という用語を避けることは良い考えかもしれません。

于 2009-11-18T14:35:54.847 に答える
23

ジェネレーターは、イテレーターを作成するための省略形と考えることができます。これらは Java イテレータのように動作します。例:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

これが役立つことを願っています/あなたが探しているものです.

アップデート:

他の多くの回答が示しているように、ジェネレーターを作成するにはさまざまな方法があります。上記の例のように括弧構文を使用することも、yield を使用することもできます。もう 1 つの興味深い機能は、ジェネレーターが「無限」になる可能性があることです。つまり、停止しないイテレーターです。

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
... 
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...
于 2009-11-18T13:53:35.133 に答える
14

Java に相当するものはありません。

これは少し不自然な例です:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

ジェネレーターには 0 から n まで実行されるループがあり、ループ変数が 3 の倍数である場合、変数が生成されます。

ループの各反復中にfor、ジェネレーターが実行されます。ジェネレーターが初めて実行される場合は、最初から開始されます。

于 2009-11-18T13:58:13.893 に答える
8

関数 foo とジェネレーター foo(n) を明確に区別するのに役立ちます。

def foo(n):
    yield n
    yield n+1

foo は関数です。foo(6) はジェネレーターオブジェクトです。

ジェネレーター オブジェクトを使用する一般的な方法は、ループ内にあります。

for n in foo(6):
    print(n)

ループ印刷

# 6
# 7

ジェネレーターを再開可能な関数と考えてください。

yieldreturn生成された値がジェネレータによって「返される」という意味でのように動作します。ただし、return とは異なり、次回ジェネレーターが値を要求されると、ジェネレーターの関数 foo は中断したところ (最後の yield ステートメントの後) から再開し、別の yield ステートメントに到達するまで実行を続けます。

舞台裏ではbar=foo(6)、ジェネレータ オブジェクトを呼び出すときに、属性を持つように bar が定義されていnextます。

自分で呼び出して、foo から生成された値を取得できます。

next(bar)    # Works in Python 2.6 or Python 3.x
bar.next()   # Works in Python 2.5+, but is deprecated. Use next() if possible.

foo が終了すると (そして生成された値がなくなると)、呼び出しnext(bar)によって StopInteration エラーがスローされます。

于 2009-11-18T14:15:10.177 に答える
7

Stephan202 の回答に追加できる唯一のことは、David Beazley の PyCon '08 プレゼンテーション「Generator Tricks for Systems Programmers」をご覧になることをお勧めします。どこでも。これこそが、「Python は面白そうだ」から「これこそが私が探していたものだ」へと私を導いたものです。http://www.dabeaz.com/generators/にあります。

于 2009-11-18T17:54:00.703 に答える
4

イテレータとジェネレータが最初に登場したのは、約 20 年前の Icon プログラミング言語だったと思います。

アイコンの概要をお楽しみください。これにより、構文に集中することなく頭を整理することができます (アイコンはおそらく知らない言語であり、グリスウォルドは他の言語から来た人々に彼の言語の利点を説明していたため)。

ほんの数段落を読んだだけで、ジェネレーターとイテレーターの有用性がより明確になるかもしれません。

于 2009-11-18T14:53:32.413 に答える