Pythonのジェネレーターとイテレーターの違い


538

イテレータとジェネレータの違いは何ですか?それぞれのケースを使用する場合の例が参考になります。

回答:


543

iteratorはより一般的な概念です。クラスにnextメソッド(__next__Python 3の場合)とその__iter__メソッドが含まれるオブジェクトreturn self

すべてのジェネレータはイテレータですが、その逆はありません。ジェネレータは、1つまたは複数のyield式(yieldPython 2.5以前のステートメント)を含む関数を呼び出すことによって構築され、前の段落の定義に適合するオブジェクトです。iterator

やや複雑な状態維持動作を持つクラスが必要な場合、またはnext(および__iter__and __init__)以外の他のメソッドを公開する場合は、ジェネレータではなくカスタムイテレータを使用することをお勧めします。ほとんどの場合、ジェネレーター(時には、十分に単純なニーズのために、ジェネレーター)で十分です。状態の維持(妥当な制限内)は基本的に、フレームが一時停止および再開されることによって「自動的に行われる」ため、コーディングが簡単です。

たとえば、次のようなジェネレータ。

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

または同等のジェネレータ式(genexp)

generator = (i*i for i in range(a, b))

カスタムイテレータとしてビルドするには、より多くのコードが必要になります。

class Squares(object):
    def __init__(self, start, stop):
       self.start = start
       self.stop = stop
    def __iter__(self): return self
    def next(self): # __next__ in Python 3
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

ただし、もちろん、クラスSquaresを使用すると、追加のメソッドを簡単に提供できます。

    def current(self):
       return self.start

アプリケーションでこのような追加機能が実際に必要な場合。


イテレータを作成したら、どのように使用しますか?
Vincenzooo

@Vincenzoooは、何をしたいかによって異なります。for ... in ...:関数の一部として渡されるか、関数に渡されるか、呼び出しますiter.next()
Caleth

@Caleth構文を使用しようとするとエラーが発生したため、正確な構文について尋ねていましたfor..in。何かが足りなかったのかもしれませんが、少し前のことですが、解決したかどうかは覚えていません。ありがとうございました!
Vincenzooo

136

イテレータとジェネレータの違いは何ですか?それぞれのケースを使用する場合の例が参考になります。

要約すると:イテレータは__iter____next__nextPython 2では)メソッドとa を持つオブジェクトです。ジェネレーターは、イテレーターのインスタンスを作成するための簡単な組み込み方法を提供します。

収量が含まれる関数は依然として関数であり、呼び出されるとジェネレーターオブジェクトのインスタンスを返します。

def a_function():
    "when called, returns generator object"
    yield

ジェネレータ式もジェネレータを返します:

a_generator = (i for i in range(0))

より詳細な説明と例については、読み続けてください。

ジェネレーターイテレーターです

具体的には、ジェネレーターはイテレーターのサブタイプです。

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

ジェネレータはいくつかの方法で作成できます。そのための非常に一般的で簡単な方法は、関数を使用することです。

具体的には、yieldを含む関数は関数であり、呼び出されるとジェネレーターを返します。

>>> def a_function():
        "just a function definition with yield in it"
        yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function()  # when called
>>> type(a_generator)           # returns a generator
<class 'generator'>

また、ジェネレーターはイテレーターです。

>>> isinstance(a_generator, collections.Iterator)
True

イテレータイテラブルです

イテレータはイテラブルです

>>> issubclass(collections.Iterator, collections.Iterable)
True

__iter__Iteratorを返すメソッドが必要です。

>>> collections.Iterable()
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__

イテラブルのいくつかの例は、組み込みのタプル、リスト、辞書、セット、凍結されたセット、文字列、バイト文字列、バイト配列、範囲、およびメモリビューです。

>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

反復子が必要next__next__の方法を

Python 2の場合:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<pyshell#80>", line 1, in <module>
    collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next

そしてPython 3では:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__

次のiter関数を使用して、組み込みオブジェクト(またはカスタムオブジェクト)からイテレータを取得できます。

>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

この__iter__メソッドは、forループでオブジェクトを使用しようとすると呼び出されます。次に、__next__反復子オブジェクトでメソッドを呼び出して、ループの各項目を取り出します。イテレータはStopIteration、使い尽くしたときに発生し、その時点では再利用できません。

ドキュメントから

組み込み型ドキュメントのイテレータ型セクションのジェネレータ型セクションから:

Pythonのジェネレーターは、イテレータープロトコルを実装する便利な方法を提供します。コンテナーオブジェクトの__iter__()メソッドがジェネレーターとして実装されている場合、それは、__iter__()およびnext()[ __next__()Python 3の]メソッドを提供するイテレーターオブジェクト(技術的にはジェネレーターオブジェクト)を自動的に返します。ジェネレーターの詳細については、yield式のドキュメントを参照してください。

(強調が追加されました。)

これから、ジェネレーターは(便利な)タイプのイテレーターであることがわかります。

イテレータオブジェクトの例

独自のオブジェクトを作成または拡張することにより、Iteratorプロトコルを実装するオブジェクトを作成できます。

class Yes(collections.Iterator):

    def __init__(self, stop):
        self.x = 0
        self.stop = stop

    def __iter__(self):
        return self

    def next(self):
        if self.x < self.stop:
            self.x += 1
            return 'yes'
        else:
            # Iterators must raise when done, else considered broken
            raise StopIteration

    __next__ = next # Python 3 compatibility

ただし、ジェネレーターを使用してこれを行う方が簡単です。

def yes(stop):
    for _ in range(stop):
        yield 'yes'

あるいは、おそらくもっと単純なジェネレータ式(リスト内包表記と同様に機能):

yes_expr = ('yes' for _ in range(stop))

これらはすべて同じ方法で使用できます。

>>> stop = 4             
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop), 
                             ('yes' for _ in range(stop))):
...     print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...     
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes

結論

Pythonオブジェクトを反復可能なオブジェクトとして拡張する必要がある場合は、Iteratorプロトコルを直接使用できます。

ただし、ほとんどの場合、yieldジェネレータイテレータを返す関数を定義するか、ジェネレータ式を検討するのに最適です。

最後に、ジェネレーターはコルーチンとしてさらに多くの機能を提供することに注意してください。「収量」キーワードは何をするのか」に対する私の答えについて、ステートメントとともにジェネレーターについてyield詳しく説明します。


41

イテレータ:

イテレータは使用するオブジェクトです next()シーケンスの次の値を取得。

発電機:

ジェネレータは、yieldメソッドを使用して一連の値を生成または生成する関数です。

ジェネレーター関数(以下の例のnext()ex:のf場合)から返されるジェネレーターオブジェクトのすべてのメソッド呼び出しはfoo()、次の値を順番に生成します。

ジェネレータ関数が呼び出されると、関数の実行を開始することなくジェネレータオブジェクトを返します。場合next()メソッドが最初に呼び出され、それが得られた値を返す収率ステートメントに達するまで、関数が実行を開始します。利回りは、つまり最後の実行を記憶することを追跡します。そして、2番目のnext()呼び出しは前の値から続行されます。

次の例は、yieldとジェネレーターオブジェクトの次のメソッドの呼び出しとの相互作用を示しています。

>>> def foo():
...     print "begin"
...     for i in range(3):
...         print "before yield", i
...         yield i
...         print "after yield", i
...     print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0            # Control is in for loop
0
>>> f.next()
after yield 0             
before yield 1            # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

3
FYIの利回りだけが方法ではなく、それがキーワードです
Jay Parikh

25

既存の回答はいずれも公式文献の混乱を明確に扱っていないため、回答を追加します。

ジェネレータ関数は、のyield代わりにを使用して定義された通常の関数ですreturn。ジェネレータ関数が呼び出されると、ジェネレータオブジェクトを返します。これは、一種のイテレータであり、next()メソッドを持っています。あなたが電話するときnext()、ジェネレーター関数によって生成された次の値が返されます。

読み取るPythonソースドキュメントに応じて、関数またはオブジェクトは「ジェネレータ」と呼ばれる場合があります。Pythonの用語集は、一方で、発電機の機能を言うのPythonのwikiは、発電機のオブジェクトを意味します。Pythonのチュートリアルでは、非常に意味することを管理して、両方の 3文の空間での用途を:

ジェネレータは、イテレータを作成するためのシンプルで強力なツールです。これらは通常の関数のように書かれていますが、データを返したいときはいつでもyield文を使用します。next()が呼び出されるたびに、ジェネレーターは中断したところから再開します(すべてのデータ値と最後に実行されたステートメントを記憶しています)。

最初の2文はジェネレータ関数でジェネレータを識別し、3番目の文はジェネレータオブジェクトでジェネレータを識別します。

このすべての混乱にもかかわらず、明確で最終的な単語についてPython言語リファレンスを探すことができます。

収量式は、ジェネレーター関数を定義するときにのみ使用され、関数定義の本体でのみ使用できます。関数定義でyield式を使用すれば、その定義で通常の関数の代わりにジェネレーター関数を作成できます。

ジェネレータ関数が呼び出されると、ジェネレータと呼ばれるイテレータを返します。そのジェネレーターは、ジェネレーター関数の実行を制御します。

したがって、正式で正確な用法では、「修飾子」が修飾されていないということは、ジェネレーター関数ではなく、ジェネレーターオブジェクトを意味します。

上記の参照はPython 2に関するものですが、Python 3言語の参照でも同じことが言えます。ただし、Python 3の用語集では、

ジェネレータ ...通常はジェネレータ関数を指しますが、コンテキストによってはジェネレータイテレータを指す場合があります。意図した意味が明確でない場合は、完全な用語を使用することで曖昧さを回避できます。


同じ理由で、通常、クラスとそのインスタンスの間に混乱がないのと同じように、ジェネレーター関数とジェネレーターオブジェクトの間に大きな混乱はないと思います。どちらの場合も、一方を呼び出して他方を取得します。カジュアルな会話(またはすぐに書かれたドキュメント)では、どちらか一方にクラス名または「ジェネレータ」という単語を使用できます。「ジェネレーター関数」と「ジェネレーターオブジェクト」を明確に区別する必要があるのは、どちらについて話しているかというまれな状況だけです。
Blckknght 2016年

6
1.混乱してはならない理論上の理由に関係なく、この質問に対する他の回答に対するコメントは、実際の混乱が存在することを示し、解決せずに互いに否定および矛盾します。2.カジュアルな不正確さは問題ありませんが、正確で信頼できるソースは少なくともSOのオプションの1つである必要があります。現在のプロジェクトではジェネレーター関数とオブジェクトの両方を幅広く使用しており、その区別は設計とコーディングの際に非常に重要です。今使用する用語を知っておくと良いので、後で何十もの変数名やコメントを変更する必要はありません。
Paul

2
関数とその戻り値が区別されない数学の文献を想像してみてください。非公式にそれらを融合することは時々便利ですが、それはさまざまなミスのリスクを高めます。高度な現代数学は、区別が慣習、言語、および表記法で形式化されていない場合、大幅かつ不必要に妨げられます。
Paul

2
ジェネレーターやジェネレーター関数の周りを通過する高次関数は奇妙に聞こえるかもしれませんが、私にとってはそれらが登場しました。私はApache Sparkで作業しており、非常に機能的なプログラミングスタイルを実施しています。関数は、物事を成し遂げるためにあらゆる種類のオブジェクトを作成し、渡し、そして渡す必要があります。私は、どのような「発電機」を扱っているのかわからなくなってしまう状況がたくさんありました。変数名とコメントのヒントは、一貫した正しい用語を使用して、混乱を解消するのに役立ちました。あるPythonistのあいまいさが、他のPythonistのプロジェクトデザインの中心になり得ます!
Paul

1
@Paul、この回答を書いてくれてありがとう。ジェネレータオブジェクトとジェネレータ関数の違いは、目的の動作を取得することとジェネレータをルックアップする必要があることの違いであるため、この混乱は重要です。
ブルジェイ2016

15

誰もが例を挙げて本当に素晴らしく冗長な答えを持っているので、本当に感謝しています。私はまだ概念的にまだはっきりしていない人々のために短い数行の答えを出したかっただけです:

独自のイテレータを作成する場合、少し複雑になります。クラスを作成し、少なくともイテレータと次のメソッドを実装する必要があります。しかし、この手間をかけたくなくて、すぐにイテレータを作成したい場合はどうでしょう。さいわい、Pythonはイテレータを定義するためのショートカット方法を提供します。必要なのは、yieldを少なくとも1回呼び出す関数を定義することだけです。この関数を呼び出すと、「some」が返され、イテレータのように機能します(次のメソッドを呼び出してforループで使用できます)。この何かはPythonでジェネレーターと呼ばれる名前を持っています

それが少し明確になることを願っています。


10

以前の回答はこの追加を逃しました:ジェネレーターにはcloseメソッドがありますが、一般的なイテレーターにはありません。このcloseメソッドStopIterationは、ジェネレーターで例外をトリガーします。これはfinally、そのイテレーターの節でキャッチされる可能性があり、クリーンアップを実行する機会を得ます。この抽象化により、単純なイテレータよりも大きなイテレータで最も使いやすくなります。ファイルを閉じるのと同じように、下にあるものを気にすることなくジェネレータを閉じることができます。

とは言っても、最初の質問に対する私の個人的な答えは次のとおりです。反復可能には__iter__メソッドのみがあり、一般的な反復子には__next__メソッドのみがあり、ジェネレーターには__iter__と、__next__およびの両方がありcloseます。

2番目の質問については、私の個人的な答えは次のとおりです。パブリックインターフェイスでは、弾力性があるため、ジェネレーターを非常に好む傾向があります。このclose方法は、とのより優れた構成可能性yield fromです。ローカルでは、反復子を使用できますが、それがフラットで単純な構造であり(反復子が簡単に構成できない)、特にシーケンスが最後に到達する前に停止される可能性がある場合は、シーケンスがかなり短いと思われる理由がある場合に限ります。私はイテレータをリテラル以外の低レベルのプリミティブと見なす傾向があります。

制御フローの問題では、ジェネレーターは約束と同じくらい重要な概念です。どちらも抽象的で構成可能です。


作曲について話すときの意味を説明する例を挙げていただけますか?また、あなたが話しているときにあなたが心に留めていることを説明できますか?典型的なイテレータ」か?
bli

1
別の回答(stackoverflow.com/a/28353158/1878788)では、「イテレータはイテラブルである」と述べています。イテラブルには__iter__メソッドがあるので、イテレーターにしか持て__next__ないのはなぜですか?それらが反復可能であると想定されている場合、私は彼らが必ず持っていることを期待します__iter__ます。
bli

1
@bli:AFAICSここでのこの回答は標準のPEP234を指しているので正しいのですが、他の回答はいくつかの実装を指しているので疑問です。標準では、(Python3の)メソッド__iter__のみを必要とするイテレータを返すためにonイテラブルのみが必要です。標準(ダックタイピング用)とその実装(特定のPythonインタープリターによる実装)を混同しないでください。これは、ジェネレーター関数(定義)とジェネレーターオブジェクト(実装)の混乱に少し似ています。;)next__next__
Tino、

7

ジェネレータ関数、ジェネレータオブジェクト、ジェネレータ:

ジェネレータ関数は、単にPythonで通常の関数のようなものですが、それは一個の以上含まれているyield文を。ジェネレータ関数は、イテレータオブジェクトをできるだけ簡単に作成するための優れたツールです 。イテレータジェネレータ関数によってオブジェクトreturendも呼ばれるジェネレータオブジェクトまたはジェネレータ

この例では、Generatorオブジェクトを返すGenerator関数を作成しました<generator object fib at 0x01342480>。他のイテレータと同じように、Generatorオブジェクトはforループで使用するかnext()、ジェネレータから次の値を返す組み込み関数で使用できます 。

def fib(max):
    a, b = 0, 1
    for i in range(max):
        yield a
        a, b = b, a + b
print(fib(10))             #<generator object fib at 0x01342480>

for i in fib(10):
    print(i)               # 0 1 1 2 3 5 8 13 21 34


print(next(myfib))         #0
print(next(myfib))         #1
print(next(myfib))         #1
print(next(myfib))         #2

したがって、ジェネレーター関数は、Iteratorオブジェクトを作成する最も簡単な方法です。

イテレータ

ジェネレーターオブジェクトイテレーターですが、その逆はありません。カスタムイテレータオブジェクトは、そのクラスが実装し__iter____next__メソッド(イテレータプロトコルとも呼ばれる)を実装している場合に作成できます 。

ただし、ジェネレーター関数を使用すると、イテレーターを簡単に作成できるため、イテレーターを作成する方がはるかに簡単ですが、カスタムイテレーターを使用すると自由度が増し、次の例に示すように、要件に応じて他のメソッドを実装することもできます。

class Fib:
    def __init__(self,max):
        self.current=0
        self.next=1
        self.max=max
        self.count=0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count>self.max:
            raise StopIteration
        else:
            self.current,self.next=self.next,(self.current+self.next)
            self.count+=1
            return self.next-self.current

    def __str__(self):
        return "Generator object"

itobj=Fib(4)
print(itobj)               #Generator object

for i in Fib(4):  
    print(i)               #0 1 1 2

print(next(itobj))         #0
print(next(itobj))         #1
print(next(itobj))         #1

6

イテレータとジェネレータに強く推奨されるNed Batchelderの

偶数に何かを行うジェネレータのないメソッド

def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them

ジェネレーターを使って

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n
  • リストreturnステートメントも必要ありません
  • 大規模/無限長のストリームに効率的...歩くだけで価値を生み出す

evensメソッド(ジェネレーター)の呼び出しは通常どおりです

num = [...]
for n in evens(num):
   do_smth(n)
  • ジェネレーターはダブルループを解除するためにも使用されます

イテレータ

ページがいっぱいの本は反復可能、ブックマークは 反復子

このブックマークは移動以外には何もしません next

litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration  (Exception) as we got end of the iterator

ジェネレータを使用するには...関数が必要です

Iteratorを使用するには...我々は必要nextiter

言われたように:

ジェネレーター関数はイテレーターオブジェクトを返します

イテレーターの全体的な利点:

一度に1つの要素をメモリに保存する


最初のコードスニペットについて、リスト[]以外にarg 'stream'が何であるか知りたいのですが?
Iqra。

5

同じデータに対して両方のアプローチを比較できます。

def myGeneratorList(n):
    for i in range(n):
        yield i

def myIterableList(n):
    ll = n*[None]
    for i in range(n):
        ll[i] = i
    return ll

# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
    print("{} {}".format(i1, i2))

# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

また、メモリフットプリントを確認すると、ジェネレータはすべての値を同時にメモリに格納する必要がないため、メモリの消費量が大幅に少なくなります。


1

私は、Pythonの初心者向けに、非常に単純な方法で具体的に書いていますが、Pythonの深いところまでは、非常に多くのことを行います。

非常に基本的なものから始めましょう:

リストを考えて、

l = [1,2,3]

同等の関数を書きましょう:

def f():
    return [1,2,3]

o / p of print(l): [1,2,3]&o / p ofprint(f()) : [1,2,3]

リストを反復可能にしましょう:Pythonでは、リストは常に反復可能です。つまり、必要なときにいつでも反復子を適用できます。

リストにイテレータを適用しましょう:

iter_l = iter(l) # iterator applied explicitly

関数を反復可能にします。つまり、同等のジェネレーター関数を作成します。 あなたがキーワードを導入するとすぐにPythonでyield; これはジェネレーター関数になり、反復子が暗黙的に適用されます。

注:すべてのジェネレーターは、暗黙のイテレーターが適用されていれば常に反復可能であり、ここでは暗黙のイテレーターが重要です。 したがって、ジェネレーター関数は次のようになります。

def f():
  yield 1 
  yield 2
  yield 3

iter_f = f() # which is iter(f) as iterator is already applied implicitly

したがって、関数faジェネレーターを作成するとすぐに、すでにiter(f)であることがわかります。

さて、

lはリストです。反復子メソッド「iter」を適用すると、iter(l)になります。

fはすでにiter(f)であり、反復子メソッド「iter」を適用した後、それはiter(iter(f))になり、これは再びiter(f)になります。

これは、intを既にint(x)にキャストしているため、int(x)のままになります。

たとえば、o / p of:

print(type(iter(iter(l))))

です

<class 'list_iterator'>

これはPythonであり、CまたはC ++ではないことを忘れないでください

したがって、上記の説明からの結論は次のとおりです。

リストl〜= iter(l)

ジェネレーター関数f == iter(f)

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