Pythonを勉強し始めると,初期にはリスト”[ ] ”表記を学びます.
そしてだんだんと,リストの内包表記を学び始めて効率的なコードが書けるようになります.
そして次には,「ジェネレータが便利だよ!」という声が耳に入りますが,結局ジェネレータってなんなん・・・となるのではないでしょうか.
このジェネレータの便利な点の初歩をまとめました.
ジェネレータの意義
ジェネレータを用いる意義の1つとしてあげられるのが
・使用メモリ量を抑えられる
という点です.これがリスト表記との決定的な違いであり,データサイエンスで大量のデータを読み込む必要があるときに効力を発揮します.
ジェネレータ表記
>>> [2 * num for num in range(5)]
[0, 2, 4, 6, 8]
>>> (2 * num for num in range(5))
<generator object <genexpr> at 0x109ce7840>
上と下の違いは,外側の括弧が"[]"であるか"()"であるかです.
そしてこの違いは,上がリストを返したのに対して,下はジェネレータオブジェクトを返すことになりました.
どちらも繰り返し可能(iterable)なので中のデータを取り出すことができます.
例えば...
result = (num for num in range(5))
for num in result:
print(num)
0
1
2
3
4
result = (num for num in range(5))
print(list(result))
[0, 1, 2, 3, 4]
一点厄介なのが,リスト表記と違って,index指定でデータが取り出せないことです.
したがって,"next()"を使って1つずつ取り出すことになります.
result = (num for num in range(5))
>>> print(next(result))
0
>>> print(next(result))
1
>>> print(next(result))
2
>>> print(next(result))
3
「こんなことしか出来ないなら,リストの内包表記でいいじゃn..」
となるかもしれません.
さてここからが重要です.まだ数が少ないからよいものの,大規模なデータを扱う際にリストの内包表記でそれが出来るか?という問題があります.
>>> [num for num in range(10**100000000)]
は実行不可能ですが
>>> (num for num in range(10**100000000))
はOKです.
つまりこうすることで,「ジェネレータオブジェクトを作成」し,あとはここからポンポンとデータを取り出していくようなイメージですね.
これでメモリ使用量を抑えることが出来るため,大規模データにも対応できることになります!
例えば内包表記を使って
even_nums = (num for num in range(5) if num %2 == 0)
print(list(even_nums))
[0, 2, 4]
などと取り出せますね.