pythonのイテレータとジェネレータについて

pythonでのイテレータとジェネレータについてのまとめ。

はじめに

イテレータとジェネレータの特徴について短くまとめると以下のようになります。

  • イテレータ
    配列のような集合的データ構造に含まれている要素を順に1個ずつ取り出せるオブジェクト。要素を取り出す際に、どこまで取り出されているかという状態を記憶しています。
  • ジェネレータ
    イテレータを作る関数, yieldで要素を生成できる

イテラブルとイテレータ

まずイテレータについて説明するためにイテラブルについて紹介します。 Pythonにおけるリスト、文字列、タプル、辞書等はイテラブルです。イテラブルとは要素を順に取り出すことができるオブジェクトのことです。 例えばリストであれば以下のようにfor文で一個ずつ要素を順に取り出せます。

numbers=[1,2,3]
for num in numbers:
    print(num)
#実行結果: 1 2 3

このイテラブルはイテレータと似ていますが、要素をどこまで取り出したかを記憶しているかという点で異なります。例えば、for文でリストの1つ目の要素を取り出した際、リスト自体はどの要素まで取り出されたか記憶していません。一方、イテレータでは要素を取り出した際に何個目までの要素を取り出したかを状態として保存しています。

イテラブルからイテレータを作る

リストからイテレータを作ってみます。 イテラブルからイテレータを作る際はiter()を使うとできます。

numbers=[1,2,3]
numbers_iter=iter(numbers)
type(numbers_iter)
#実行結果: <class 'list_iterator'>

続いて、作ったイテレータから要素を取り出していきます。要素を取り出す際にはnext()を使います。

>>> next(numbers_iter)
1
>>> next(numbers_iter)
2
>>> next(numbers_iter)
3
>>> next(numbers_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

クラスでイテレータを自作する

クラスを定義して自分でイテレータを作ってみます。

class SampleIterator(object):
     def __init__(self, *args): # 引数名に*を付けると可変長引数として扱われます
         self._numbers = args
         self._i = 0
     def __iter__(self):
         return self
     def __next__(self):
         if self._i == len(self._numbers):
             raise StopIteration()
         value = self._numbers[self._i]
         self._i += 1
         return value
sample_iterator(1,2,3)

sample_iterator = SampleIterator(1,2,3)
for num in sample_iterator:
     print(num)

# 1
# 2
# 3

ジェネレータ関数でイテレータを作る

ジェネレータを使うとクラスを書かなくても簡単にイテレータを作ることができます。以下に例を示します。

def my_generator(*args):
    for i in args:
        yield i

g = my_generator(1,2,3)
next(g)
# 1
next(g)
# 2
next(g)
# 3
next(g)
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
# StopIteration

next()を使わなくても, またfor文で以下のように要素を取り出すことができます。

g = my_generator(1,2,3)
for num in g:
     print(num)

# 1
# 2
# 3

ジェネレータを使うメリットとしては以下の2つが挙げられます。

  • 必要なときに要素を生成するため, 計算コストやメモリの節約につながる
  • あらかじめ繰り返す要素をすべて生成できない場合にも使える

あと, yieldって関数のreturnと似ているので勘違いしやすいのですが,

  • returnは要素を返した後, 関数呼び出しが終了
  • yieldは値を返した後も返す要素がまだあれば終了しない

という違いがあります。

ジェネレータのまとめ

  • ジェネレータはyieldを使って実装する
  • yieldした回数だけ要素が生成される
  • ジェネレータ関数を関数呼び出しするとイテレータのオブジェクトになる

参考文献

大重 美幸, 詳細! Python 3 入門ノート, ソーテック社, 2017/5/24