イテレータとジェネレータ
イテレータとジェネレータ
Python を学んでいくなかで混乱しやすいものに、反復可能オブジェクトとイテレータ、ジェネレータがあります。
反復可能オブジェクト:
リスト、タプル、文字列などのシーケンス型オブジェクト
オブジェクトには __iter__ メソッドを持っている
イテレータ(Iterator) :
反復可能オブジェクトの一種
オブジェクトには __iter__ と __next__ メソッドを持っている
組み込み関数 next() で呼ばれたときに次の要素を返す
ジェネレータ関数(generator function):
ジェネレータイテレータオブジェクトを返す関数
yield文がある関数
ジェネレータイテレータ(generator iterator) :
ジェネレータ関数で生成されるオブジェクト
反復可能オブジェクトやイテレータオブジェクトはfor文で馴染みがあるものです。
ここで、なぜジェネレータが必要になってくるのかを考えてみましょう。
巨大なデータを扱う必要が出てきたとして、単純にリストなどに保持してしまうと、すべてがメモリ上に展開されてしまい、コンピュータリソースの制約(つまりメモリ不足)により処理できなくなることもありえます。
こうしたときに、ジェネレータを使うと都度オブジェクトを生成しながら要素を返すことができるので、使用メモリを節約することができます。
リスト内包表記と間違いやすいジェネレータ
リスト内包表記とよく似た記述になりますが、タプルで使用する丸カッコ(())で囲むと
タプル内包表記ではなくジェネレータとなります。
次の例を見てみましょう。
code: 0601_generator_and_comprehension.py
print(type(data))
print(data)
data = ( i for i in range(10) )
print(type(data))
print(data)
これを実行すると次のようになります。
code: bash
$ python 0601_generator_and_comprehension.py
<class 'list'>
<class 'generator'>
<generator object <genexpr> at 0x101dcf410>
内包表記の説明でイテラルオブジェクトについて触れましたが、Python の内部処理をもう少し詳しく説明すると、イテレータはオブジェクトに自身を指し示す __iter__メソッドを持ち、一度に1つだけ要素を返します。
code: python
>> data = list()
>> '__iter__' in dir(data)
True
>>
yield文
yield文を使ってジェネレータ関数を作ることができます。return文は関数を終了させて指定した値を返しますが、yield文は関数を一時停止させて指定した値を返します。
まずは、例を見てましょう。
code: 0602_generator_with_yeild.py
def simpleGenerator():
yield 1
yield 2
yield 3
for value in simpleGenerator():
print(value)
関数simpleGenerator() はyeild文によって要素を一つずつ返すジェネレータ関数となり、for文と要素を取り出しながら処理されます。
次の例を見ると、yeild文が関数の処理を一時停止させているのが理解しやすいでしょう。
code: 0603_generator_with_yeild_and_forever_loop.py
def mysquare():
i = 1;
while True: # 無限ループ
yield i*i
i += 1
for num in mysquare():
if num > 30:
break
print(num)
関数 mysquare() は1から順に数値を上げていき、その2乗した値を返す関数です。
問: yield文の箇所が return文だったらどうなるか考えてみましょう。
さて、次の例は、フィボナッチ数列を生成する関数 myfibo() を yield文を使って記述した例です。
呼び出し方では、ジェネレータオブジェクトから次の要素を取り出す next() 関数を使う方法、ジェネレータオブジェクトの__next__メソッドを使って要素を取り出す方法、for 文に与えて逐次要素を最後まで取り出す方法、の3つを示しています。
code: 0604_generator_with_yeild_and_next.py
def fibonacci(max):
a, b = 1, 1
a, b = b, a + b
while a < max:
yield a
a, b = b, a + b
x = fibonacci(30)
print(type(x))
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(next(x))
print(next(x))
print(next(x))
print('Call with for loop')
for i in fibonacci(30):
print(i)
yield from式
yield from式を使うと複数のジェネレータ関数やイテレータオブジェクトを束ねるジェネレータ関数とすることができます。
次の例は、range() が生成する反復可能オブジェクトとリストオブジェクトをまとめた
ジェネレータ関数 mygenerator() を定義したものです。
code: 0605_generator_with_yeild_from.py
def mygenerator():
yield from range(5)
for d in mygenerator():
print(d)
zip()
zip() は複数のイテラブル・オブジェクトを受け取って、それぞれのイテラブルから要素を集めたタプルの イテレータ を生成します。
このとき、N番目のタプルは各イテラブルオブジェクトの N 番目の要素を集めたタプルになります。
タプルは next() を使って順番に取り出すことができます。
code: 0606_zip_iterator.py
queen = zip(members, musical_instruments)
for d in queen:
print(d)
code: bash
$ python 0606_zip_iterator.py
('Freddie Maer', 'Vocal')
('Brian May', 'Guitar')
('John Deacon', 'Bass')
('Roger Taylor', 'Drums')
zip() に与えるイテレータの要素数が異なる場合は、少ない方の数が採用されて、長い方の要素は取り出せません。
code: 0607_zip_iterator_different_size.py
members.append('Adam Lambert')
queen = zip(members, musical_instruments)
for d in queen:
print(d)
code: bash
$ python 0607_zip_iterator_different_size.py
('Freddie Mercury', 'Vocal')
('Brian May', 'Guitar')
('John Deacon', 'Bass')
('Roger Taylor', 'Drums')