web-dev-qa-db-ja.com

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

イテレータとジェネレータの違いは何ですか?各ケースを使用する場合の例がいくつかあります。

455

iteratorはより一般的な概念です。クラスがnextメソッド(Python 3では__next__)と__iter__を実行するreturn selfメソッドを持つオブジェクトです。

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

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

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

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

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

460
Alex Martelli

イテレータとジェネレータの違いは何ですか?各ケースを使用する場合の例がいくつかあります。

要約すると、反復子は__iter__および__next__(Python 2ではnext)メソッドを持つオブジェクトです。ジェネレータはイテレータのインスタンスを作成するための簡単な組み込みの方法を提供します。

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

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

ジェネレータ式はジェネレータも返します。

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

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

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

特に、generatorはiteratorのサブタイプです。

>>> 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__メソッドを必要とします。

>>> 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を発生させます、そしてそれはその時点で再利用することができません。

ドキュメントから

組み込み型のIterator TypesセクションのGenerator Typesセクションから documentation

Pythonのジェネレータは、イテレータプロトコルを実装するための便利な方法を提供します。コンテナオブジェクトの__iter__()メソッドがジェネレータとして実装されている場合、それは自動的に__iter__()およびnext() [Python 3の__next__()]メソッド。ジェネレータについてのより多くの情報は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"キーワードは何をするのですか?」という私の答えについて、[yield]文とともにGeneratorsについて説明します。

105
Aaron Hall

イテレータ:

イテレータはnext()メソッドを使用してsequenceの次の値を取得するオブジェクトです。

ジェネレータ:

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

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

ジェネレータ関数が呼び出されると、その関数の実行を開始せずにジェネレータオブジェクトを返します。 next()メソッドが最初に呼び出されたとき、その関数はyieldされた値を返すyield文に達するまで実行を開始します。歩留まりは、すなわち最後の実行を記憶していることを追跡する。そして2番目のnext()呼び出しは前の値から継続します。

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

>>> 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
>>>
31
user966588

既存の答えのどれも公式の文学における混乱に特に対処していないので答えを追加する。

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

どのPythonソース文書を読んだかに応じて、関数またはオブジェクトのどちらかを「ジェネレータ」と呼ぶことがあります。 Python用語集 はジェネレータ関数を表し、 Python wiki はジェネレータオブジェクトを意味します。 Pythonチュートリアル は、3つの文の間に両方の用法を意味しています。

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

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

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

Yield式は、生成関数を定義するときにだけ使用され、関数定義の本体内でのみ使用できます。関数定義でyield式を使用すると、その定義で通常の関数の代わりに生成関数を作成するのに十分です。

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

したがって、正式かつ厳密な使用法では、「ジェネレータ」は、ジェネレータ関数ではなくジェネレータオブジェクトを意味します。

上記の参照はPython 2用ですが Python 3言語参照 は同じことを言っています。しかし、 Python 3用語集 は、

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

18
Paul

誰もが例を挙げて本当に素晴らしく冗長な答えを持っています、そして私は本当にそれを感謝します。概念的にまだ明確ではない人々のために、私はほんの数行の答えを出したいと思いました。

あなた自身のイテレータを作成する場合、それは少し複雑です - あなたはクラスを作成し、少なくともiterと次のメソッドを実装する必要があります。しかし、この面倒なことをしたくなくて、すぐにイテレーターを作成したい場合はどうなりますか。幸い、Pythonはイテレータを定義するための近道です。あなたがする必要があるのはyieldに少なくとも1回の呼び出しで関数を定義することです、そして今あなたがその関数を呼び出すときそれはイテレータのように振る舞う "something"を返すでしょう。 forループで)これはsomethingという名前でPythonでGeneratorと呼ばれています

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

8
Heapify

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

--- ジェネレータ関数はPythonの通常の関数とまったく同じですが、1つ以上のyieldステートメントが含まれています。ジェネレータ関数は、イテレータオブジェクトをできるだけ簡単に作成するための優れたツールです。ジェネレータ関数によるイテレータオブジェクトの戻り値は、ジェネレータオブジェクトまたはジェネレータとも呼ばれます。

この例では、Generatorオブジェクト<generator object fib at 0x01342480>を返すGenerator関数を作成しました。他のイテレータと同様に、Generatorオブジェクトはforループ内またはgeneratorから次の値を返す組み込み関数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__メソッド(イテレータプロトコルとも呼ばれる)を実装している場合は、カスタムイテレータオブジェクトを作成できます。

しかし、作成にはgenerator関数を使うほうがはるかに簡単ですiteratorsこれらは作成を簡単にしますが、カスタムIteratorを使用すると自由度が増し、以下に示すように必要に応じて他のメソッドを実装できます。例です。

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
N Randhawa

以前の回答ではこの追加を見逃していました。ジェネレータにはcloseメソッドがありますが、典型的なイテレータにはありません。 closeメソッドはジェネレータ内でStopIteration例外を発生させます。これはそのイテレータのfinally句で捕捉され、何らかのクリーンアップを実行する機会を得ることができます。この抽象化は、単純なイテレータよりも大規模なイテレータで最も有用になります。ファイルを閉じることができるのと同じようにジェネレータを閉じることができます。

そうは言っても、最初の質問に対する私の個人的な答えは、iteratableには__iter__メソッドだけがあり、典型的なイテレーターには__next__メソッドだけがあり、ジェネレーターには__iter____next__および追加のcloseがあります。

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

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

5
Hibou57

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

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)))

その上、あなたがメモリフットプリントをチェックするならば、ジェネレータはそれがメモリにすべての値を同時に格納する必要がないのではるかに少ないメモリを使います。

4
tashuhka

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

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

イテレータを使用するには... nextiterが必要です。

言われたように:

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

イテレータの全メリット

1要素ずつメモリに保存する

0
Marwan Mostafa