実行しようとしていたのはこんなコードでした。
>>> import re
>>> s = "hogefugapiyofoobarbaz1234567890abc987efg654hij321"
>>> iter = re.finditer("b..", s) ← finditer()は結果をイテレータで返す
>>> for i in iter:
... print(i.start())
...
15
18
32
>>> for i in iter:
... print(i.start())
...
>>> ← 同じループを実行しても最初のループと異なり結果が返ってこない
このように、同一のイテレータに対しループ処理を複数回行うと、2回目以降のループは結果が空になってしまいます。
ちなみにジェネレータでも上記のような複数回のループ処理を行おうとすると、2回目以降のループで結果が空になるらしいですが、ジェネレータについては別途まとめて記事にしようと思います(まだ勉強中)。
イテレータが持つ要素を取得したい場合、__next__() メソッド(または組み込み関数のnext()
)を繰り返し呼び出すと、イテレータ中の要素を1つずつ返します。このメソッドは集合から1つずつ要素を取り出しています。取り出しているので、すべて取り出し終わったら元の集合には要素が存在しません。よって2回目以降のループは空っぽになります(要素がない場合は、StopIteration例外を返す)。
※「取り出す」という表現が正確かどうかはちょっと自信がありません。メソッドや関数の「next」という名前の通り「次の要素へ」という挙動と、同じ要素を複数回取得できないことから「取り出す」という表現を使っています。
なお、直接関係はありませんが、map()
やfilter()
はイテレータを返す(Python3での話)ので、返されたオブジェクトについてlist()
などを複数回実行すると、上記のように2回目以降は空っぽになってしまうようです。
>>> list = [1, 2, 3]
>>> f = filter(None, list)
>>> list(list)
[1, 2, 3]
>>> list(list)
[1, 2, 3] ← リストlistに複数回listしても結果が返ってくる
>>> list(f)
[1, 2, 3]
>>> list(f)
[] ← イテレータに複数回listすると2回目以降ブランクになる
>>>
iteratorとはオブジェクトの一種で、データの走査方法について表現するものです。なんのこっちゃ、という感じですが「要素を1つずつ繰り返し取得できる構造を持っていて(iterable
)、実際に順次取得ができる」オブジェクトっていう感じかと。
>>> list = [1, 2, 3]
>>> i = iter(list)
>>> type(list)
<class 'list'>
>>> type(i)
<class 'list_iterator'>
>>> print(next(i))
1
>>> print(next(i))
2
>>> print(next(i))
3
>>> print(next(i))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration ← 「これ以上next()で取り出せる要素ねぇよ!」と言っている
>>>
これと似たオブジェクトにitetable(反復可能オブジェクト)があります。こちらはデータの構造そのものについて表現しており、iterator
とは別物です。たとえばリストやタプル、辞書などはiterable
で、オブジェクトに対しアクセスすることで、要素を1つずつ取得することができる構造のことを指しています。つまり、先述のiterator
はiterable
に含まれるわけです。
ちなみに「iterable」の英単語本来の意味は「繰り返し可能な」という形容詞。
再利用したいなら、単純に変数へ格納しちゃえという方法。
>>> import re
>>> s = "hogefugapiyofoobarbaz1234567890abc987efg654hij321"
>>> iter = re.finditer("b..", s)
>>> type(iter)
<class 'callable_iterator'>
>>> lists = list(iter)
>>> type(lists)
<class 'list'>
>>> for i in lists:
... print(i)
...
<_sre.SRE_Match object; span=(15, 18), match='bar'>
<_sre.SRE_Match object; span=(18, 21), match='baz'>
<_sre.SRE_Match object; span=(32, 35), match='bc9'>
>>> for i in lists:
... print(i.start())
...
15
18
32
>>> for i in lists:
... print(i.start())
...
15
18
32 ← 複数回ループしても結果が返ってきている
>>>
filter()
などのイテレータを返すものも同様。
>>> list = [1, 2, 3]
>>> f = filter(None, list)
>>> type(f)
<class 'filter'>
>>> listed = list(f)
>>> type(listed)
<class 'list'>
>>> list(listed)
[1, 2, 3]
>>> list(listed)
[1, 2, 3]
>>> list(f)
[]
>>>
今回の場合で言うとfinditer()
ではなくfindall()
を用いて、イテレータでなくリストでループするようにします。
>>> list = re.findall("b..", s)
>>> for item in list:
... print(item)
...
bar
baz
bc9
>>> for item in list:
... print(item)
...
bar
baz
bc9
findall()
はリストを返すメソッド。リストlist
に対しては、ループ処理を何回行っても同様な結果が出力されます。これなら上記のような問題は発生しませんが、このあたりは要求される機能と相談する必要があると思います。
再利用する回数が事前に分かっているならitertools.tee()
を利用する方法もあります。
>>> import itertools
>>> list = [1, 2, 3]
>>> i = iter(list)
>>> i1, i2, i3 = itertools.tee(i, 3) ← 3回再利用する必要があると仮定
>>> for n in i1:
... print(n)
...
1
2
3
>>> for n in i1:
... print(n)
...
>>> for n in i2:
... print(n)
...
1
2
3
>>> for n in i2:
... print(n)
...
>>> for n in i3:
... print(n)
...
1
2
3
>>> for n in i3:
... print(n)
...
>>>
ただ、個人的にはこの方法を利用するようなシチュエーションがあまり思い浮かばない・・・。
「ん?何で同じ条件なのにループすると空っぽになるんじゃ?」と素朴に思ったのが始まりなのですが、調べてみると案外深い仕様になっていて勉強になりました(小並感)。
ちなみに、複数回ループしようとしてた理由は、原因を調べているうちに忘れました(鳥頭)。