大量のデータや複数のコレクションを扱うとき、毎回リストを作ってしまうとメモリと時間に無駄が出ます。
標準ライブラリのitertools
は、イテレータのまま要素を流し込み、必要な分だけ取り出す設計になっているため、処理を軽くできます。
ここではchain
、islice
、permutations
に絞って、初心者の方にもわかりやすく具体例で解説します。
itertoolsの基本
itertoolsとは?イテレーション効率化の標準ツール
itertools
は、Python標準ライブラリに含まれるイテレータ用の便利ツール群です。
イテレータは要素を逐次的に生成する仕組みで、すべてを一度にメモリへ展開しません。
つまり「必要なときに必要な分だけ」計算する遅延評価を行うため、大きなデータでも省メモリで扱えます。
本記事では次の3つにフォーカスします。
chain
複数イテラブルの連結islice
一部だけの切り出しpermutations
順列の生成
- itertools — 効率的なループ用のイテレータ生成関数群 — Python 3.13.7 ドキュメント
- 関連記事:ジェネレータの基本と使い方: 遅延評価で省メモリ処理
- 関連記事:複雑なforループは卒業!itertoolsで多重ループ整理
importと基本の使い方
インポートは2通りあります。
必要な関数だけを明示的にインポートするのが読みやすくておすすめです。
# 1. モジュールごと
import itertools
# 2. 必要な関数だけ(推奨)
from itertools import chain, islice, permutations
# 簡単なデモ: chainで連結 → isliceで先頭3件 → タプル化
data1 = [1, 2]
data2 = (3, 4)
first3 = tuple(islice(chain(data1, data2, range(5, 10)), 3))
print(first3) # (1, 2, 3)
(1, 2, 3)
イテレータは一度消費すると使い回せない(再利用不可)ことが多い点も頭に入れておくと安全です。
- 関連記事:0から9まで回すには?range()の基本と実例10選
- itertools — 効率的なループ用のイテレータ生成関数群 — Python 3.13.7 ドキュメント
- 関連記事:0から9まで回すには?range()の基本と実例10選
いつ使う?listより省メモリに
list
へ変換すると全要素をメモリに展開します。
データが巨大な場合や、I/Oから逐次読み込む場合は、itertools
でイテレータのまま処理する方が効率的です。
処理途中で必要な部分だけを見る、または早期に打ち切る用途に特に向いています。
chainの使い方(複数イテラブルを連結)
複数のリストやタプルを1つに連結
itertools.chain
は、複数のイテラブルを「つなげて」一つの連続したシーケンスのように扱えます。
要素はコピーされず、順に読み出されるだけなので無駄がありません。
from itertools import chain
a = [1, 2]
b = (3, 4)
c = "56" # 文字列もイテラブル(1文字ずつ取り出されます)
# chainは与えた順に取り出します
for x in chain(a, b, c):
print(x, end=" ")
print()
1 2 3 4 5 6
文字列を1要素として扱いたい場合は[c]
のようにリストに入れて渡します。
chain.from_iterableとの違い
chain
は複数の引数を直接受け取りますが、chain.from_iterable
は「イテラブルの中に、さらにイテラブルが入っている」形(ネスト)を1引数で受け取ります。
どちらも結果は同じです。
from itertools import chain
nested = [[1, 2], (3, 4), range(5, 7)]
print(list(chain.from_iterable(nested))) # ネストを1引数で
print(list(chain(*nested))) # アンパックして複数引数で
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
生成器(ジェネレータ)のように「順次イテラブルが出てくる」ケースではfrom_iterable
が自然です。
from itertools import chain
def gen():
# 各イテレーションで「イテラブル」を1つずつ返す
for i in range(3):
yield range(i, i + 2)
print(list(chain.from_iterable(gen())))
[0, 1, 1, 2, 2, 3]
listの+やextendとの違い
chain
はイテレータを返し、要素をコピーしないのが最大の違いです。
+
やextend
はリストに特化した操作で、データを実体化して結合します。
以下の比較を押さえておくと良いです。
方法 | 戻り値/副作用 | メモリ | 備考 |
---|---|---|---|
chain(a, b) | イテレータ(非破壊) | 小さい | あらゆるイテラブルを連結。1回きりの読み出しが基本 |
a + b | 新しいリスト | 結合分コピー | リスト専用。元は変わらず新しく確保 |
a.extend(b) | 破壊的にa を拡張 | 必要分のみ確保 | リスト専用。元のa が変更される |
from itertools import chain
a = [1, 2]
b = [3, 4]
c = a + b # 新しいリストを作る
print("c:", c)
a.extend(b) # aを書き換える(破壊的)
print("a:", a)
it = chain([1, 2], [3, 4]) # 遅延評価のイテレータ
print(list(it)) # 必要になったときだけ展開
c: [1, 2, 3, 4]
a: [1, 2, 3, 4]
[1, 2, 3, 4]
大量データでも省メモリ
巨大な配列を作らずに処理を先へ流せるのがchain
の強みです。
次の例では、1千万件のrange
とジェネレータを連結していますが、先頭5件だけを取り出して即終了できます。
from itertools import chain, islice
big1 = range(10_000_000) # rangeは遅延生成する
big2 = (x * x for x in range(10_000_000)) # 自前のジェネレータ
print(list(islice(chain(big1, big2), 5))) # 先頭5件だけ
[0, 1, 2, 3, 4]
「必要な分だけ」消費する設計により、メモリのピークを低く抑えられます。
isliceの使い方(一部だけ取り出す)
start stop stepの指定
islice
は、任意のイテラブルから範囲指定で要素を取り出す関数です。
islice(iterable, stop)
またはislice(iterable, start, stop[, step])
の形で使います。
stopは含まれない点はリストのスライスと同じです。
from itertools import islice
print(list(islice(range(10), 4))) # 0..3の4件
print(list(islice(range(10), 2, 9, 3))) # 2,5,8 (start=2, stop=9, step=3)
[0, 1, 2, 3]
[2, 5, 8]
listのスライスとの違い
islice
は「イテラブル」なら何にでも使え、遅延的に取り出すのが特徴です。
一方でlist
のスライスはすでにあるリストから部分配列を新規作成します。
観点 | listのスライス | itertools.islice |
---|---|---|
対象 | リストのみ | ほぼ全イテラブル |
メモリ | 新しいリストを作る | 遅延評価で必要分のみ |
インデックス | 負数可、逆順可 | 負数や逆順は不可 |
代入 | スライス代入可 | 代入は不可(読み出しのみ) |
from itertools import islice
seq = [0, 1, 2, 3, 4, 5]
print(seq[2:5:2]) # リストのスライス
print(list(islice(seq, 2, 5, 2))) # isliceでも同様に取り出せる
[2, 4]
[2, 4]
先頭N件だけ読む(ファイル/ジェネレータ)
ファイルを全部読むのではなく、先頭だけを見るのにislice
は最適です。
ここではStringIO
でファイルっぽい振る舞いを再現します。
from io import StringIO
from itertools import islice
fake_file = StringIO("a\nb\nc\nd\ne\n")
for line in islice(fake_file, 3): # 先頭3行だけ読む
print(line.strip())
a
b
c
CSVのヘッダ行スキップやログの冒頭だけ確認など、日常的に役立ちます。
無限イテレーションの制御
itertools.count
などの無限イテレータもislice
で安全に区切れます。
from itertools import count, islice
print(list(islice(count(10, 2), 5))) # 10から2刻み、先頭5件
[10, 12, 14, 16, 18]
「無限列×islice」で有限にするのは実務の常套手段です。
permutationsの使い方(順列を生成)
基本の使い方と戻り値
permutations(iterable, r=None)
は、要素の順列をタプルとして生成するイテレータを返します。
戻り値はイテレータであり、各要素はタプルです。
from itertools import permutations
items = ["A", "B", "C"]
for p in permutations(items): # rを省略すると全長の順列(3! = 6通り)
print(p)
('A', 'B', 'C')
('A', 'C', 'B')
('B', 'A', 'C')
('B', 'C', 'A')
('C', 'A', 'B')
('C', 'B', 'A')
r引数で長さを指定
r
を与えると、その長さの順列だけを生成します。
from itertools import permutations
items = ["A", "B", "C"]
for p in permutations(items, 2): # 3P2 = 6通り
print("".join(p))
AB
AC
BA
BC
CA
CB
重複要素の扱い
permutations
は「位置」を区別するため、入力に重複があると重複した順列も出力されます。
from itertools import permutations
items = ["A", "A", "B"]
print(len(list(permutations(items)))) # 3! = 6 (重複を含む)
print(list(permutations(items, 2))) # 重複したタプルが並ぶ
# ユニークな順列にしたいならsetを介す(大きい集合ではメモリに注意)
unique = sorted(set(permutations(items)))
print(unique)
6
[('A', 'A'), ('A', 'B'), ('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'A')]
[('A', 'A', 'B'), ('A', 'B', 'A'), ('B', 'A', 'A')]
重複排除にはset
が手軽ですが、全順列を一度に展開するため入力が大きいとメモリを消費します。
組み合わせ爆発に注意
順列の数は階乗で増えます。
少し大きいだけで現実的な計算量を超えます。
必ず「数を見積もる」か「islice
で一部だけ見る」などの対策を取りましょう。
from math import factorial
for n in range(3, 8):
print(f"n={n}, n!={factorial(n)}")
n=3, n!=6
n=4, n!=24
n=5, n!=120
n=6, n!=720
n=7, n!=5040
先頭だけ確認するテクニックです。
from itertools import permutations, islice
for p in islice(permutations(range(10), 3), 5): # 10P3の先頭5件だけ
print(p)
(0, 1, 2)
(0, 1, 3)
(0, 1, 4)
(0, 1, 5)
(0, 1, 6)
「大きな順列はリスト化しない」「必要な分だけ消費する」を徹底すると安全です。
まとめ
itertoolsは「遅延評価」でイテレーションを効率化する標準ツールです。
chain
は複数のイテラブルを無駄なく連結し、islice
は任意範囲だけを取り出し、permutations
は順列をタプルで生成します。
どれも「必要な分だけ使う」設計のため、巨大データや無限列でも安全に扱えます。
実務では、リスト化を最小限に抑え、isliceで早期に打ち切るのが鉄則です。
組み合わせ爆発しがちなpermutations
は特に規模の見積もりと部分的なサンプリングを心がけてください。
慣れてくると、コードは短く、メモリと時間は軽く、そして意図は明確に表現できるようになります。