Pythonのitertools.count, cycle, repeatによる無限イテレータ
Pythonの標準ライブラリitertoolsモジュールの関数itertools.count()
, itertools.cycle()
, itertools.repeat()
を使うと、無限に続くイテレータを生成できる。
ここでは以下の内容について説明する。
itertools.count()
: 無限カウントitertools.cycle()
: リストなどの要素を無限繰り返しitertools.repeat()
: 同じ値を無限繰り返し
それぞれの関数について、for文による繰り返し処理の例とzip()
関数との組み合わせの例を紹介する。
for文およびzip()
関数についての詳細は以下の記事を参照。
for文で使う場合、終了条件を指定しbreak
しないと無限ループに陥るので要注意。
while文でもカウンターなどを伴う無限ループを実装できるが、itertoolsの各関数を使うとスッキリ書けることが多い。
itertools.count(): 無限カウント
itertools.count()
は無限にカウントアップまたはカウントダウンする値を返すイテレータを生成する。
デフォルトは開始値が0
で1
ずつ増加。
import itertools
for i in itertools.count():
print(i)
if i > 3:
break
# 0
# 1
# 2
# 3
# 4
第一引数start
で開始値、第二引数step
で増分を指定可能。
for i in itertools.count(2):
print(i)
if i > 3:
break
# 2
# 3
# 4
for i in itertools.count(step=3):
print(i)
if i > 8:
break
# 0
# 3
# 6
# 9
for i in itertools.count(2, 3):
print(i)
if i > 8:
break
# 2
# 5
# 8
# 11
カウントダウンしたいときはstep
に負の値を指定すればよい。
for i in itertools.count(10, -1):
print(i)
if i < 8:
break
# 10
# 9
# 8
# 7
浮動小数点数float
での指定も可能だが、掛け算を使ったほうが正確になる場合がある(らしい)。
浮動小数点数でカウントするときは (start + step * i for i in count()) のように掛け算を使ったコードに置き換えたほうが正確にできることがあります。 itertools.count() --- イテレータ生成関数 — Python 3.8.0 ドキュメント
for i in itertools.count(0.1, 1.5):
print(i)
if i > 3:
break
# 0.1
# 1.6
# 3.1
for i in itertools.count():
ii = 0.1 + 1.5 * i
print(ii)
if ii > 3:
break
# 0.1
# 1.6
# 3.1
zip()
と組み合わせるとカウンターを追加したタプルを生成できる。
l1 = ['a', 'b', 'c']
l2 = ['x', 'y', 'z']
print(list(zip(itertools.count(), l1, l2)))
# [(0, 'a', 'x'), (1, 'b', 'y'), (2, 'c', 'z')]
なお、enumerate()
とzip()
の場合は入れ子のタプルになる。
print(list(enumerate(zip(l1, l2))))
# [(0, ('a', 'x')), (1, ('b', 'y')), (2, ('c', 'z'))]
forループで使う場合はenumerate()
とzip()
でも以下のようにそれぞれの値を展開して使用できる。
names = ['Alice', 'Bob', 'Charlie']
ages = [24, 50, 18]
for i, (name, age) in enumerate(zip(names, ages)):
print(i, name, age)
# 0 Alice 24
# 1 Bob 50
# 2 Charlie 18
itertools.cycle(): リストなどの要素を無限繰り返し
itertools.cycle()
は引数に指定したリストなどのイテラブルオブジェクトの要素を無限に繰り返すイテレータを生成する。
l = [1, 10, 100]
sum_value = 0
for i in itertools.cycle(l):
print(i)
sum_value += i
if sum_value > 300:
break
# 1
# 10
# 100
# 1
# 10
# 100
# 1
# 10
# 100
range()
を引数に指定した例。
- 関連記事: Pythonのrange関数の使い方
sum_value = 0
for i in itertools.cycle(range(3)):
print(i)
sum_value += i
if sum_value > 5:
break
# 0
# 1
# 2
# 0
# 1
# 2
zip()
と組み合わせた例。
l1 = [1, 10, 100]
l2 = [0, 1, 2, 3, 4, 5, 6]
print(list(zip(itertools.cycle(l1), l2)))
# [(1, 0), (10, 1), (100, 2), (1, 3), (10, 4), (100, 5), (1, 6)]
なお、itertools.cycle()
は元のイテラブルオブジェクトのコピーを生成する。元のイテラブルオブジェクトのサイズが大きいとメモリを大量に使用してしまうので注意。
cycle() は大きなメモリ領域を使用します。使用するメモリ量は iterable の大きさに依存します。 itertools.cycle() --- イテレータ生成関数 — Python 3.8.0 ドキュメント
itertools.repeat(): 同じ値を無限繰り返し
itertools.repeat()
は同じ値を無限に返すイテレータを生成する。
sum_value = 0
for i in itertools.repeat(10):
print(i)
sum_value += i
if sum_value > 40:
break
# 10
# 10
# 10
# 10
# 10
第二引数times
で繰り返し回数を指定可能。
for i in itertools.repeat(10, 3):
print(i)
# 10
# 10
# 10
なお、便宜上「値」と書いたが、第一引数にはどんなオブジェクトでも指定できる。使いどころがあるかどうかは分からないが、関数オブジェクト(例では組み込み関数len()
)を繰り返すことも可能。
for l in itertools.repeat([0, 1, 2], 3):
print(l)
# [0, 1, 2]
# [0, 1, 2]
# [0, 1, 2]
for func in itertools.repeat(len, 3):
print(func('abc'))
# 3
# 3
# 3
zip()
と組み合わせた例は以下の通り。定数の要素を追加できる。
l = [0, 1, 2, 3]
print(list(zip(itertools.repeat(10), l)))
# [(10, 0), (10, 1), (10, 2), (10, 3)]