itertools とは、ループ処理でよく使う「つなぐ・切り出す・まとめる・組み合わせる」操作を、メモリを節約しながら行うための Python 標準ライブラリです。 import itertools だけで使え、どの関数も結果をまとめて作らず、必要になった分だけ1つずつ返すイテレータ(next() で順に値を取り出せるオブジェクト)を返します。

この記事では、itertools の全21関数を「無限イテレータ・終端するイテレータ・組合せ」の3分類で一覧表にし、それぞれ1行のコピペ例と出力を Python 3.11.15 と 3.14.7 で実行して載せています。 さらに、groupby がソートしないとまとまらない落とし穴の再現、ページ分割・移動平均・総当たり・重複除去・フラット化などの用途別レシピ、イテレータが1回しか回せないことと tee の注意点まで、実行結果つきで解説します。

itertools全関数の早見表

下の表のコードは from itertools import *(accumulate の例は import operator も)を前提に、そのまま実行できます。 出力は Python 3.14.7 の実行結果です。3.11 でも batched 以外は同じ出力でした。

無限イテレータ(止めないと終わらない)

関数 何をするか コピペ例 出力
count(start, step) 数を無限に数える list(islice(count(10, 5), 4)) [10, 15, 20, 25]
cycle(it) 要素を無限に繰り返す list(islice(cycle('AB'), 5)) ['A', 'B', 'A', 'B', 'A']
repeat(x, n) 同じ値を n 回(省略で無限) list(repeat('x', 3)) ['x', 'x', 'x']

終端するイテレータ(入力が尽きたら終わる)

関数 何をするか コピペ例 出力
accumulate(it, func) 累積(既定は合計) list(accumulate([1, 2, 3, 4])) [1, 3, 6, 10]
〃(関数指定) 累積の最大値など list(accumulate([3, 1, 4, 1, 5], max)) [3, 3, 4, 4, 5]
〃(initial) 初期値つき(3.8+) list(accumulate([1, 2, 3], operator.mul, initial=10)) [10, 10, 20, 60]
batched(it, n) n 個ずつに分ける(3.12+) list(batched('ABCDEFG', 3)) [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]
〃(strict=True) 端数をエラーに(3.13+) list(batched('ABCDEFG', 3, strict=True)) ValueError: batched(): incomplete batch
chain(*its) 複数をつなぐ list(chain('AB', [1, 2])) ['A', 'B', 1, 2]
chain.from_iterable(it) 入れ子を1段平らに list(chain.from_iterable([[1, 2], [3], []])) [1, 2, 3]
compress(it, selectors) 真の位置だけ残す list(compress('ABCD', [1, 0, 1, 0])) ['A', 'C']
dropwhile(pred, it) 条件が真の間は捨てる list(dropwhile(lambda x: x < 3, [1, 2, 3, 1, 4])) [3, 1, 4]
filterfalse(pred, it) 条件が偽のものだけ list(filterfalse(lambda x: x % 2, range(6))) [0, 2, 4]
groupby(it, key) 連続する同じ値をまとめる [(k, list(g)) for k, g in groupby('AAABBA')] [('A', ['A', 'A', 'A']), ('B', ['B', 'B']), ('A', ['A'])]
islice(it, start, stop, step) スライス list(islice('ABCDEFG', 1, 6, 2)) ['B', 'D', 'F']
pairwise(it) 隣どうしのペア(3.10+) list(pairwise([1, 2, 3, 4])) [(1, 2), (2, 3), (3, 4)]
starmap(func, it) タプルを展開して関数へ list(starmap(pow, [(2, 3), (3, 2)])) [8, 9]
takewhile(pred, it) 条件が真の間だけ取る list(takewhile(lambda x: x < 3, [1, 2, 3, 1, 4])) [1, 2]
tee(it, n) 1つから n 個に複製 [list(t) for t in tee([1, 2, 3], 2)] [[1, 2, 3], [1, 2, 3]]
zip_longest(*its, fillvalue) 長いほうに合わせて zip list(zip_longest('ABC', [1], fillvalue='-')) [('A', 1), ('B', '-'), ('C', '-')]

組合せイテレータ(総当たり・順列・組合せ)

関数 何をするか コピペ例 出力
product(*its, repeat) 直積(入れ子 for の代わり) list(product('AB', [1, 2])) [('A', 1), ('A', 2), ('B', 1), ('B', 2)]
〃(repeat) 同じものを repeat 回 len(list(product([0, 1], repeat=3))) 8
permutations(it, r) 順列(順番を区別) list(permutations('ABC', 2)) [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
combinations(it, r) 組合せ(順番を区別しない) list(combinations('ABC', 2)) [('A', 'B'), ('A', 'C'), ('B', 'C')]
combinations_with_replacement(it, r) 重複ありの組合せ list(combinations_with_replacement('AB', 2)) [('A', 'A'), ('A', 'B'), ('B', 'B')]

やりたいことから引く逆引き表です。

やりたいこと 使う関数
リストを n 件ずつに分けたい(ページ分割・バッチ処理) batched(3.11 以前は後述の代替コード)
2重・3重の for ループを1つにしたい product
入れ子のリストを1段平らにしたい chain.from_iterable
前の要素との差分・隣どうしを見たい pairwise
累計・累積最大を出したい accumulate
同じキーの要素をまとめて集計したい sorted → groupby
長さの違うリストを揃えて並べたい zip_longest
先頭 n 件だけ取りたい(ジェネレータ) islice
条件を満たす間だけ処理したい takewhile / dropwhile

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python itertoolsの使い方|全関数の一覧と用途別コード例まとめ」

3分類を図で理解する

itertools の関数は、入力と出力の関係で3つに分けると覚えやすくなります。

flowchart TB
    A["itertools"] --> B["無限イテレータ<br>count / cycle / repeat"]
    A --> C["終端するイテレータ<br>入力が尽きたら終わる<br>chain / islice / groupby など"]
    A --> D["組合せイテレータ<br>product / permutations<br>combinations など"]
    B --> E["islice や zip で<br>止める必要がある"]
    C --> F["入力1つに対して<br>加工した値を返す"]
    D --> G["件数が急に増えるので<br>件数を先に計算する"]
分類 特徴 注意点
無限 自分では止まらない list(count()) は終わらないので、islice・zip・break で止める
終端 入力の長さに合わせて終わる 入力がイテレータなら、消費すると元に戻らない
組合せ 件数が掛け算で増える math.comb(n, r)・math.perm(n, r) で件数を先に確認する

組合せの件数は math モジュールで事前に計算できます。

import math
from itertools import combinations

print(len(list(combinations(range(10), 3))), math.comb(10, 3), math.perm(10, 3))

実行結果(Python 3.11.15):

120 120 720

batchedの使い方とPython 3.11以前の代替コード

batched は Python 3.12 で追加された「n 件ずつに分ける」関数で、3.13 で strict 引数が追加されました。 Python 3.11 で import すると次のエラーになります。

ImportError: cannot import name 'batched' from 'itertools' (unknown location)

3.11 以前でも、islice を使った同じ動きの関数で代用できます。

from itertools import islice

def batched(iterable, n):
    it = iter(iterable)
    while batch := tuple(islice(it, n)):
        yield batch

print(list(batched("ABCDEFG", 3)))

実行結果(Python 3.11.15):

[('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]

strict=True(3.13+)は、最後の組が n 件に満たないときに ValueError を出します。 「必ず3件ずつの組でないと困る」データ(3列で1レコードなど)の検査に便利です。

バージョン batched の状態
3.11 以前 なし(上の代替コードを使う)
3.12 batched(iterable, n)
3.13 以降 batched(iterable, n, *, strict=False)

groupbyはソートしないとまとまらない

groupby は「隣り合って連続している同じキー」だけをまとめます。 SQL の GROUP BY のように全体から同じキーを集めるわけではないので、先に同じキーで sorted() しておく必要があります。

ソートせずに使った場合(同じキーが分かれる)

from itertools import groupby
from operator import itemgetter

orders = [
    {"item": "pen", "qty": 2},
    {"item": "note", "qty": 1},
    {"item": "pen", "qty": 3},
    {"item": "note", "qty": 4},
    {"item": "clip", "qty": 1},
]
for k, g in groupby(orders, key=itemgetter("item")):
    print(k, sum(o["qty"] for o in g))

実行結果(Python 3.11.15 / 3.14.7):

pen 2
note 1
pen 3
note 4
clip 1

pen と note が2回ずつ出ています。並び順のまま、キーが変わるたびに新しいグループになるためです。

同じキーで先にソートした場合(正しくまとまる)

key = itemgetter("item")
for k, g in groupby(sorted(orders, key=key), key=key):
    print(k, sum(o["qty"] for o in g))
clip 1
note 5
pen 5

ソートの key と groupby の key は同じものを使うのがポイントです。並べ替えの詳しい書き方は「Python sorted・sortのkey指定」の記事でまとめています。

グループは次に進むと中身が消える

groupby が返すグループ g は、次のグループに進んだ時点で使えなくなります。 後で使うなら、その場で list(g) にしておきます。

from itertools import groupby

groups = [(k, g) for k, g in groupby("AABBB")]
print([(k, list(g)) for k, g in groups])

groups = [(k, list(g)) for k, g in groupby("AABBB")]
print(groups)
[('A', []), ('B', [])]
[('A', ['A', 'A']), ('B', ['B', 'B', 'B'])]

「連続している」性質を活かす使い方

逆に、連続する同じ値を数えたいとき(ランレングス)は、ソートせずにそのまま使います。

from itertools import groupby

print([(k, len(list(g))) for k, g in groupby("AAABCCDDDD")])
[('A', 3), ('B', 1), ('C', 2), ('D', 4)]
目的 ソート 代わりの手段
同じキーをすべて集めて集計 必要 collections.defaultdict(list)、Counter
連続する同じ値をまとめる 不要(並びを保つ) なし(groupby が最適)
表データの集計 ― pandas の groupby

ソートの手間(O(n log n))が気になる大量データの「全体集計」は、defaultdict で1回のループにまとめる方法も選べます。

用途別レシピ集(すべて実行結果つき)

ここからは、よくある処理を itertools で書くレシピです。Python 3.14.7 で実行しました(batched を使うものは 3.12 以降、それ以外は 3.11 でも同じ結果)。

リストをページ分割する(n件ずつ)

from itertools import batched, islice

articles = [f"記事{i}" for i in range(1, 8)]
for page, rows in enumerate(batched(articles, 3), start=1):
    print(page, rows)

def page_of(iterable, page, size):
    start = (page - 1) * size
    return list(islice(iterable, start, start + size))

print(page_of(articles, 3, 3))
1 ('記事1', '記事2', '記事3')
2 ('記事4', '記事5', '記事6')
3 ('記事7',)
['記事7']

全ページを順に処理するなら batched、特定のページだけ欲しいなら islice です。islice はリストでなくジェネレータやファイルにも使えます。

移動平均・スライディングウィンドウを作る

sliding_window は公式ドキュメントの「Itertools Recipes」に載っている関数で、deque(maxlen=n) で直近 n 件だけを保持します。

from collections import deque
from itertools import islice, accumulate, pairwise

def sliding_window(iterable, n):
    it = iter(iterable)
    window = deque(islice(it, n - 1), maxlen=n)
    for x in it:
        window.append(x)
        yield tuple(window)

sales = [10, 20, 30, 40, 50]
print(list(sliding_window(sales, 3)))
print([sum(w) / len(w) for w in sliding_window(sales, 3)])

# 累積和を使う移動平均(件数が多いとき速い)
def moving_avg(xs, n):
    s = list(accumulate(xs, initial=0))
    return [(s[i + n] - s[i]) / n for i in range(len(xs) - n + 1)]

print(moving_avg(sales, 3))

# 前日との差分
print([b - a for a, b in pairwise(sales)])
[(10, 20, 30), (20, 30, 40), (30, 40, 50)]
[20.0, 30.0, 40.0]
[20.0, 30.0, 40.0]
[10, 10, 10, 10]

accumulate(xs, initial=0) で累積和 [0, 10, 30, 60, ...] を作ると、区間の合計が「引き算1回」で求まるため、ウィンドウが大きくても計算量が増えません。

総当たり(入れ子のforを1つにする)

from itertools import product

sizes = ["S", "M"]
colors = ["赤", "青"]
for combo in product(sizes, colors):
    print(combo)

# 0〜9 の数字3つで合計が25になる組み合わせ
hit = [(a, b, c) for a, b, c in product(range(10), repeat=3) if a + b + c == 25]
print(hit)
('S', '赤')
('S', '青')
('M', '赤')
('M', '青')
[(7, 9, 9), (8, 8, 9), (8, 9, 8), (9, 7, 9), (9, 8, 8), (9, 9, 7)]

product(range(10), repeat=3) は for a in range(10): for b in range(10): for c in range(10): と同じ順番で値を返します。入れ子が深くなるほど読みやすさの差が大きくなります。

選び方 関数 3つから2つ選ぶ件数
順番を区別する、同じものは選ばない permutations 6
順番を区別しない、同じものは選ばない combinations 3
順番を区別しない、同じものも選べる combinations_with_replacement 6
各位置で全部から選ぶ product(..., repeat=2) 9

順序を保ったまま重複を取り除く

公式レシピの unique_everseen は、最初に出た順番を保ったまま重複を除きます。 key で「大文字小文字を無視して重複判定」などもできます。

def unique_everseen(iterable, key=None):
    seen = set()
    for x in iterable:
        k = x if key is None else key(x)
        if k not in seen:
            seen.add(k)
            yield x

print(list(unique_everseen("ABBCA")))
print(list(dict.fromkeys("ABBCA")))
print(list(unique_everseen(["Pen", "pen", "Note"], key=str.lower)))
['A', 'B', 'C']
['A', 'B', 'C']
['Pen', 'Note']

key が不要で要素がハッシュ可能(dict のキーにできる)なら、list(dict.fromkeys(...)) の1行が一番手軽です。set() は順番が保たれないので、順番を保ちたいときはこの2つを使います。

方法 順序 key 指定 巨大データ(ストリーム)
list(set(xs)) 保たない 不可 全件をメモリへ
list(dict.fromkeys(xs)) 保つ 不可 全件をメモリへ
unique_everseen(xs, key) 保つ 可 1件ずつ処理できる
[k for k, _ in groupby(xs)] 保つ 可 連続する重複だけ除く

入れ子のリストをフラットにする

1段だけ平らにするなら chain.from_iterable が速く、読みやすい書き方です。 何段も入れ子になっているなら再帰のジェネレータを使います。

from itertools import chain

nested = [[1, 2], [3], [4, 5]]
print(list(chain.from_iterable(nested)))

def deep_flatten(xs):
    for x in xs:
        if isinstance(x, list):
            yield from deep_flatten(x)
        else:
            yield x

print(list(deep_flatten([1, [2, [3, [4]]]])))
[1, 2, 3, 4, 5]
[1, 2, 3, 4]

1段のフラット化を3つの書き方で計測しました(10要素×1000個のリスト、Python 3.11.15、100回×5セットの最小値)。

書き方 1回あたり
list(itertools.chain.from_iterable(data)) 0.099 ms
[x for row in data for x in row] 0.137 ms
sum(data, []) 10.299 ms

sum(data, []) はリストを足すたびに新しいリストを作り直すため、件数が増えると急に遅くなります。 chain.from_iterable か内包表記を使うとよいです。

縦横を入れ替える(転置)

from itertools import zip_longest

print(list(zip(*[(1, 2, 3), (4, 5, 6)])))
print(list(zip_longest(*[[1, 2, 3], [4]], fillvalue=0)))
[(1, 4), (2, 5), (3, 6)]
[(1, 4), (2, 0), (3, 0)]

長さが違うと zip は短いほうに合わせて切り捨てます。 足りない部分を埋めたいときは zip_longest を使います。逆に「長さが違ったらエラーにしたい」場合は、組み込みの zip(..., strict=True)(3.10+)が使えます。

条件を満たす最初のn件だけ取る

from itertools import islice, count

def take(n, it):
    return list(islice(it, n))

print(take(3, (x * x for x in count(1) if x % 2)))
[1, 9, 25]

count(1) は無限に続きますが、islice で3件取った時点で止まるので、無限ループになりません。

公式レシピ集とmore-itertools

Python 公式ドキュメントの itertools ページ末尾には「Itertools Recipes」という関数集があり、コピーして使えます。 主なものは次のとおりです(3.14 のドキュメントで確認)。

分類 レシピ名の例
基本 take、prepend、tail、consume、nth、quantify、first_true、all_equal、flatten
データ処理 unique_everseen、unique_justseen、sliding_window、grouper、roundrobin、iter_except
数学 powerset、sum_of_squares
統計(移動統計) running_mean、running_min、running_max、running_median

これらを毎回コピーするのが手間なら、PyPI の more-itertools にまとまっています。 標準ライブラリではないので pip install more-itertools で入れます。

pip install more-itertools
import more_itertools as mi

print(list(mi.chunked(range(7), 3)))          # リストで n 件ずつ
print(list(mi.windowed(range(5), 3)))         # スライディングウィンドウ
print(list(mi.unique_everseen("ABBCA")))      # 順序を保った重複除去
print(mi.first([], default="なし"))            # 先頭(空なら既定値)
print(list(mi.collapse([1, [2, [3, [4]]]])))  # 何段でもフラット化
print(mi.ilen(x for x in range(10) if x % 3 == 0))  # イテレータの件数

実行結果(more-itertools 11.1.0 / Python 3.11.15):

[[0, 1, 2], [3, 4, 5], [6]]
[(0, 1, 2), (1, 2, 3), (2, 3, 4)]
['A', 'B', 'C']
なし
[1, 2, 3, 4]
4

標準の batched はタプル、more_itertools.chunked はリストを返します。 Python 3.12 以降なら、まずは標準の batched で足りるか検討するとよいです。

イテレータは1回しか回せない

itertools の関数が返すイテレータは、最後まで読むと空になり、もう一度最初から読むことはできません。 map・zip・ジェネレータ式も同じです(ジェネレータそのものの仕組みは「Pythonジェネレータとyield」の記事を参照してください)。

it = map(str.upper, ["a", "b"])
print(list(it))
print(list(it))

g = (x * x for x in range(3))
print(sum(g), sum(g))

z = zip([1, 2], [3, 4])
print(len(list(z)), len(list(z)))

実行結果(Python 3.11.15 / 3.14.7):

['A', 'B']
[]
5 0
2 0

エラーにならず空になるだけなので、気づきにくいのがつまずきポイントです。何度も使うなら list() で一度リストにしておきます。

状況 対処
結果を2回以上使う、件数が少ない list() にして保存する
件数が多い、1回のループで済む イテレータのまま使う
2つの処理で並行して読みたい tee を使う(下記の注意あり)
最初からやり直したい 元のリストから iter() を作り直す

teeの注意点:元のイテレータを触らない

tee は1つのイテレータから独立した複数のイテレータを作ります。片方が先に進んでも、もう片方は最初から読めます。

from itertools import tee

src = iter([1, 2, 3, 4])
a, b = tee(src)
print(next(a), next(a))
print(list(b))
1 2
[1, 2, 3, 4]

ただし tee した後に元の src を直接進めると、その値は a にも b にも届きません。

src = iter([1, 2, 3, 4])
a, b = tee(src)
next(src)              # 元を直接進めてしまう
print(list(a), list(b))
[2, 3, 4] [2, 3, 4]
tee のポイント 内容
元のイテレータ tee した後は使わない(変数を上書きするとよい:src, copy = tee(src))
メモリ 進み具合の差の分だけ値を内部に保持する。片方を全部読んでからもう片方を読むなら list() のほうが速くて簡単
スレッド 公式ドキュメントでは、tee のイテレータはスレッドセーフではないとされている

Python 3.14ではitertoolsのpickle・copyが使えなくなった

Python 3.12 で非推奨になっていた itertools オブジェクトの pickle・copy・deepcopy 対応が、3.14 で削除されました。 同じコードを両方で動かした結果です。

import pickle, copy
from itertools import count

pickle.dumps(count(5))
c = copy.copy(count(5))
バージョン pickle.dumps(count(5)) copy.copy(count(5))
3.11.15 成功 成功
3.14.7 TypeError: cannot pickle 'itertools.count' object TypeError: cannot pickle 'itertools.count' object

itertools のオブジェクトを保存・複製していたコードは、list() で値にしてから保存するか、作り直すための引数(count なら現在の数)を保存する形に書き換えます。

よくある質問

Q. itertoolsを使うと速くなりますか? A. 多くの関数は C 言語で実装されているため、同じ処理を Python の for ループで書くより速いことが多いです。 フラット化の計測でも chain.from_iterable が内包表記より約3割速い結果でした。ただし一番の利点は、全件をリストにせず1件ずつ処理できる「メモリの節約」と「読みやすさ」です。

Q. groupbyで同じキーがまとまらないのはなぜですか? A. groupby は隣り合った同じキーしかまとめないためです。 先に sorted(data, key=同じkey) で並べ替えてから使います。並べ替えたくない場合は collections.defaultdict(list) で集める方法が向いています。

Q. batchedが使えません(ImportError)。 A. batched は Python 3.12 以降の関数です。 3.11 以前では、記事内の islice を使った代替関数か、more_itertools.chunked を使います。strict=True は 3.13 以降です。

Q. itertoolsの結果を表示すると <itertools.chain object at ...> と出ます。 A. イテレータは中身をまだ計算していない状態なので、そのままでは値が表示されません。 確認するときは list() で囲みます。無限イテレータ(count・cycle・引数なしの repeat)は list(islice(..., 10)) のように件数を区切ってから表示します。

まとめ

  • itertools は「無限(count・cycle・repeat)」「終端(chain・islice・groupby など)」「組合せ(product・permutations・combinations)」の3分類、全21関数で覚えると引きやすい
  • groupby は連続する同じキーだけをまとめる。全体を集計するなら同じ key で先に sorted() する
  • batched は 3.12+、strict は 3.13+。3.11 以前は islice の代替関数で同じことができる
  • ページ分割は batched、移動平均は sliding_window か accumulate、総当たりは product、フラット化は chain.from_iterable
  • イテレータは1回しか回せない。何度も使うなら list()、tee した後は元のイテレータを使わない
  • 3.14 では itertools オブジェクトの pickle・copy が削除されたので、値にしてから保存する
  • 公式の Itertools Recipes と more-itertools で、よく使う処理を短く書ける

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python itertoolsの使い方|全関数の一覧と用途別コード例まとめ」

株式会社RJCでは、itertools のような標準ライブラリの使い方を、仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?