最近、イテレータについて勉強したので書いてみようと思います。
イテレータとは、要素を反復して取り出すことの配列みたいな型です。
は?ですね、はい。
反復処理
公式ドキュメントによると、反復処理メソッドをサポートするための型と書いてあります。1
要するに、動的にオブジェクトを生成する変数(配列)みたいなものですかね。
ジェネレーター
このドキュメントにはジェネレーターなるものも書かれています。
この解説を見るに、ジェネレーターはイテレータの一種で、呼び出すごとに何か処理をするイテレータなのだとわかります。2
これに関しては後述します。
配列をイテレーターにしてみる
とりあえず、手を動かしてみました。
season = ['Spring', 'Summer', 'Fall', 'Winter']
iter_season = iter(season) # イテレーターに変換
print(type(iter_season)) # ここでiterと出る。イテレーターになっている。
print(next(iter_season)) # 1番目のイテレータを表示後、次のイテレータに進む
next(iter_season) # 次のイテレータに進む
# イテレータを1つずつ取り出して表示
for i in iter_season:
print(i)
# イテレータを1つずつ取り出して表示
for i in iter_season:
print(i)
これをすると、
<class 'list_iterator'>
Spring
Fall
Winter
となります。
なんでしょうか...感覚としては一度きりしか呼び出せない配列のような気分です。
reversed関数
iter()
ではただイテレーターにするだけでしたが、reversed()
ではどうでしょう。
season = ['Spring', 'Summer', 'Fall', 'Winter']
iter_season = reversed(season)
print(type(rev_season)) # 型を表示して確認
print(next(iter_season)) # 1番目のイテレータを表示後、次のイテレータに進む
next(iter_season) # 次のイテレータに進む
# イテレータを1つずつ取り出して表示
for i in iter_season:
print(i)
# イテレータを1つずつ取り出して表示
for i in iter_season:
print(i)
<class 'list_reverseiterator'>
Winter
Summer
Spring
逆になっているのがわかりますね。
reversedを使うと逆向きのイテレータが作ることができるようです。
注意点
先ほども書いたように、イテレータオブジェクトは使い捨てなので、一度呼び出し切った場合はもう一度作成し直さなければならないようです。
自作のイテレータクラス
ここまでは標準で用意されている型をイテレータに変換してきましたが、今度は自分で作ったクラスをイテレータとして実装してみましょう。
class MyIter:
def __init__(self, objs):
self.objs = objs # コンストラクタの引数をオブジェクトとして持つ
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.objs):
raise StopIteration # 配列の中身がなくなったらStopIteration例外を投げる
value = self.objs[self.index] # index番めの要素をセット
index = self.index # indexをセット
self.index += 2 # indexを増やす。次に next()されたときは増えた状態になっている。
return index, value
my_iter = MyIter(['Apple','Orange','Strawberry','Peach','Banana'])
for i in my_iter:
print(i)
こうすると、結果は、
(0,'Apple')
(2,'Strawberry')
(3,'Banana')
となります。
ジェネレーターとはなんぞや
ジェネレーターとは、さっきのやつの関数バージョンのようなことらしいです。
def MyGen(obj):
index = 0
while True:
if self.index >= len(self.obj):
raise StopIteration
value = self.obj[self.index]
index = self.index
yield index, value
my_gen = MyGen(['Apple','Orange','Strawberry','Peach','Banana'])
print(my_gen[0])
print(my_gen[1])
とすると、結果は、
(0,'Apple')
(2,'Strawberry')
と同じように返ってきます。
先のものとの違いは、yield
で値を返すことです。
これの何が嬉しいかというと、今回であればいつ呼び出されても同じ処理をしているが、1回目の処理と違う処理をさせることだってできることですね。
ジェネレータやイテレータの便利なところ
イテレータの便利さは一度読み取ったら、前のものはもう読み出せないことで、二重処理を防ぐことができるのではないかと思いました。
ジェネレーターに関しては、メモリを取らないことのようです。処理をして値を生成するわけだが、呼び出されない限り処理はされないので、メモリに優しいことのようですね。
自分はまだ完璧に使いこなすことはできませんが、これからも勉強していこうと思います。