イテレータ生成関数はイテラブルとして扱える関数のことです。
ループで呼び出される際に値を生成して返すようになっています。最初から全量を作っておくとメモリの無駄遣いになります。例えば連番を振るような場合を考えます。ループが完了するまで何回呼び出されるかわかりません。事前に連番を用意すると無限に連番を用意しなくてはならなくなります。forなどと組み合わせて呼び出される際に値を生成して戻すのがイテレータ生成関数です。
もちろんイテレータ生成関数を使わずにループの中でロジックを組んで自作することもできます。しかしループの中でロジックを組むと記述量が増えます。バグが混入する確率も上がります。ループの中で記述するロジック量は可能な限り減らすべきです。
pythonにはイテレータ生成関数を集めたitertoolsというモジュールがあります。その中には多数のイテレータ生成関数があるのですが、今回は無限イテレータについてみてゆきます。
目次
- 無限イテレータ
- 数を数える count関数
- イテレータ生成関数を使わない実装
- ループ状にくりかえす cycle関数
- 同じことを繰り返す repeat関数
- 無限イテレータで注意すること
- まとめ
無限イテレータ
ループが回る度に無限に値を生成することから無限イテレータとよばれています。count()、cycle()、repeat()などが無限イテレータと呼ばれます。他のイテレータのイテラブルとして用いられることもあります。
↑目次
数を数える count関数
通常の言語ではintに上限があります。pythonではsys.maxsizeで環境(OS)のintの最大値を取得できますが、python自体のintに上限はなくメモリの許す限り大きな数を計算できます(python3以降)。ですのでcount()はメモリや例外の発生しない限り無限に数をcountすることができます。試しにOSのintの最大値-1からcount関数を使って数を数えてゆきます。
from itertools import count
import sys
for i in count(sys.maxsize-1):
print(i)
実行結果
9223372036854775806
9223372036854775807
9223372036854775808
9223372036854775809
9223372036854775810
9223372036854775811
…以下Ctrl+Cで停止するまで永遠に続く
暇なときにはどこまで続くかぼーっと眺めてみるのも良いかもしれません。
↑目次
イテレータ生成関数を使わない実装
試しにイテレータ生成関数を使わずに実装してみます。
import sys
i = sys.maxsize -1
while True:
print(i)
i = i + 1
このようにカウントアップするために初期化部分をループの外に出す必要があります。またループのprint(i)の部分には本来目的とする実装を記載するのですが、目的とする実装の記述量が多くなると最後のi = i + 1の記述を漏らしてしまうことがあります。 count()を使えばこのような心配をせずにループの中は本来目的とする実装のみに集中することができます。イテレータ生成関数には本来の目的とする実装とcountするという実装を分けることで実装者の関心を分離しバグの混入を防ぐ効果があります。
次にイテレータ生成関数を使わない場合、カウントアップしているのを理解するにはwhileと i = i + 1の組合せで2行を見て初めて理解できます。一方イテレータ生成関数を使うとcountの1行を見ただけでカウントしていることを理解できます。
pythonではなく他の言語(例えばC系の言語)でも1行で理解できるように工夫されてはいます。
for ( int i = 0; true; i++ ){
print(i);
}
しかしforの一行の中で初期化をおこなったり継続条件を記載したり、カウントするためのi++があったりと本質的ではありません。
pythonではイテレータ生成関数を上手に使って簡潔に記述するようにしてください。
↑目次
ループ状に繰り返す cycle関数
ABCABCABC…や123412341234…など同じ順番を無限に繰り返します。
cycle()の例
from itertools import cycle
for i in cycle((1,2,3,4)):
print(i)
実行結果
1
2
3
4
1
2
3
4
1
2
3
4
…以下1234のループの繰り返し
↑目次
同じことを繰り返す repeat関数
repeat()は引数をくりかえします。
repeatの例
from itertools import repeat
for i in repeat((1,2,3,4)):
print(i)
実行例
(1, 2, 3, 4)
(1, 2, 3, 4)
(1, 2, 3, 4)
…以下(1, 2, 3, 4)の繰り返し
↑目次
無限イテレータで注意すること
無限イテレータを内包表記で取り出そうとするとどうなるでしょうか。怖いことになりそうですが試してみます。
(絶対に真似しないでください)
from itertools import count
l=[i for i in count()]
print(l)
実行直後
10数秒後
あわててCtrl+Cで停止しました。メモリを開放するのに時間がかかるのか10秒近くかかってやっと止まりました。無限イテレータと内包表記を組み合わせるときには以下のようにして()で囲ってジェネレータとしてつかうようにしてください。
from itertools import count
l=(i for i in count())
print(l)
実行結果
<generator object <genexpr> at 0x000001C93EF49510>
通常は上記のようにせずに以下のように直接使うのであまり意味がないかもしれません…
from itertools import count
for i in count():
print(i)
↑目次
まとめ
今回は無限イテレータの使い方についてのべました。
イテレータ関数を使わない場合for文やwhile文の中で自分でロジックを考えて実装する必要があります。当然for文やwhileの中の記述量が増し読みにくくバグが入り込む余地が生まれます。
forやwhile文のイテラブルにイテレータ生成関数を指定することでソースコードの可読性が増します。積極的に使ってみてください。
また無限イテレータを内包表記で展開しようとすると、無限にイテレータ展開しようとするため、メモリが急激に枯渇する実験も試してみました。
イテレータ生成関数には無限イテレータの他に組合せやマップ、グループ化など様々なものがあります。次回は組合せのイテレータ生成関数の一つであるpermutations関数について述べたいとおもいます。
↑目次
この記事へのコメント
コメントはまだありません。
コメントを送る