順番に読み進めながら学べます

itertools と functools — 反復と関数の合成道具箱

combinations/product/chain/accumulateの反復レシピ、partialの引数固定、@lru_cacheのメモ化による再帰高速化を実例で学べます。

itertoolsイテラブルから新しいイテラブルを作る関数群(組み合わせ / 連結 / 累積)、functools関数自体を加工する関数群(引数固定 / メモ化)。どちらも 1 行でfor ループ + 状態管理を置き換えられる場面が多く、コードを大幅に短くできます。

イテラブル — 反復可能な値

itertoolsの多くの関数は「イテラブル」を入力に取り、新しいイテラブルを返すので、まずこのイテラブルが何かを押さえておきます。イテラブル(iterable、for x in 〇〇:〇〇に書ける値)とは、forループやlist(...) / sum(...) / map(...)に渡せる値の総称です。list / tuple / str / dict / set / range、自分で書いたジェネレータyieldを使う関数)など、Python では多くの型がイテラブルとして振る舞います。

イテラブルの代表例
list[1, 2, 3]tuple(1, 2, 3)str"abc"dict{"a": 1}set{1, 2, 3}rangerange(5)
for x in 〇〇:の〇〇に書ける値がイテラブル。list / tuple / str / dict / set / rangeなどの組み込み型に加えて、yieldを使った自作のジェネレータもイテラブルとして振る舞う。

itertools — 組み合わせと反復の関数群

itertoolsイテラブルから新しいイテラブルを作る関数を集めたモジュールです。よく使うのはcombinations(順序なしの組み合わせ)/ product(デカルト積、複数集合の全組)/ chain(複数のイテラブルを 1 本に連結)/ accumulate(累積和や累積積)の 4 つで、これらを覚えておくと二重 for ループ状態を持ったループをすっきり書き換えられます。

itertools の主要 4 関数
combinations順序なしの組み合わせproduct複数集合の総当たりchainイテラブルの連結accumulate累積和・累積積
combinations順序なしのペア / トリプルproduct複数集合の総当たりchainイテラブルの連結accumulate走査しながら状態を持ち越す。どれも戻り値はイテレータなのでlist(...)で実体化する。
関数入力 → 出力
combinations(it, k)k 個ずつ取る組み合わせ(順序なし)[A,B,C] → (A,B), (A,C), (B,C)
permutations(it, k)k 個ずつ取る順列(順序あり)[A,B] → (A,B), (B,A)
product(*its)複数集合のデカルト積[S,M] × [赤,青] → 4 通り
chain(*its)複数イテラブルを連結[1,2] + [3,4] → [1,2,3,4]
accumulate(it)左から累積(既定は和)[10,20,30] → [10, 30, 60]

3 種類のドリンクから 2 種を選ぶペアと、サイズ × 色の全在庫を列挙します。combinations順序なしの組み合わせproduct複数集合のデカルト積を作ります。

① itertools からcombinationsproductを読み込んでください

② ドリンク["coffee", "tea", "juice"]から2 種を選ぶペアcombinationsで作ってリスト化し、ペア: ◯の形で表示してください

③ サイズ["S", "M", "L"]と色["red", "blue"]全組み合わせproductで作ってリスト化し、全在庫: ◯の形で表示してください

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

2 日分の売上データを連結し、累積売上を計算します。chain複数のイテラブルを 1 本に連結accumulate左から走査して累積します。

① itertools からchainaccumulateを読み込んでください

② 1 日目の売上[120, 80]と 2 日目の売上[200, 150, 90]chainで連結してリスト化し、全売上: ◯の形で表示してください

③ ② の連結結果にaccumulateを適用してリスト化し、累積売上: ◯の形で表示してください

Python エディタ

コードを実行してください

functools.partial — 引数の一部を先に固定する

functools.partial「引数の一部を先に固定した新しい関数」を作る関数です。コールバックや高階関数に引数固定済みの関数を渡したいときに、def wrapper(...): ...のような 1 行ラッパーを書く代わりに使えます。同じ関数を少しずつ違う引数で何度も呼ぶ場面で、ラッパーが減ってコードが読みやすくなります。

from functools import partial

def format_with_unit(price, unit):
    return f"{price}{unit}"

# 単位「円」を先に固定した新しい関数を作る
to_yen = partial(format_with_unit, unit="円")

# 個別の価格に適用 — 後は price だけ渡せばよい
print(to_yen(100))   # 100円
print(to_yen(200))   # 200円
print(to_yen(300))   # 300円
partial の働き
power(base, exp)base ** exp を返すpartial(power, exp=2)exp=2 を固定square(3) → 9square(5) → 25
partial(関数, 引数=値)引数の一部を先に固定した新しい関数を返す。power(base, exp)exp=2を固定すれば、後はbaseだけ渡せばよいsquare関数になる。

partial でpower(base, exp)のexpを固定し、2 乗・3 乗の専用関数を作ります

① functools からpartialを読み込んでください

def power(base, exp):で`base ** expを返す関数を定義してください

③ partial で`exp=2`を固定した`square`関数を作り、square(3)とsquare(5)を呼んで`3 の 2 乗: ◯` / `5 の 2 乗: ◯`の形で表示してください

④ partial で`exp=3`を固定した`cube`関数を作り、cube(2)を呼んで`2 の 3 乗: ◯`の形で表示してください

Python エディタ

コードを実行してください

functools.lru_cache — メモ化で計算結果をキャッシュする

「同じ引数で何度も呼ばれる重い関数を、毎回計算せず使い回したい」 — 計算結果が決まりきっている関数で速度が問題になる場面で必要になります。普通に書くと自前でキャッシュ用の dict を管理する必要がありますが、@lru_cacheならデコレータ 1 行で済みます。

@lru_cache関数の戻り値をキャッシュするデコレータです。同じ引数で再度呼ばれたとき、前回の戻り値をそのまま返すので、再計算が要らなくなります。LRU(Least Recently Used、直近で使われていないものから捨てる方式)で古いキャッシュから捨てるので、maxsize=128のような上限を指定してメモリ使用量を制限できます。

lru_cache の動き
fib(30) 初回本体を実行結果をキャッシュ+ 戻り値fib(30) 2 回目キャッシュから取得→ 即座に戻り値
初回の引数はキャッシュに無いので関数本体が実行され、結果が(引数 → 戻り値)のペアでキャッシュに保存される。2 回目以降の同じ引数はキャッシュから即座に返る — 関数本体は実行されない。

副作用のある関数には付けない

@lru_cache「同じ引数なら同じ戻り値」という前提で動きます。ファイルを読む / DB に書く / 現在時刻を返すなど副作用のある関数や、同じ引数で結果が変わる関数に付けると、1 回目の結果をずっと返し続けるバグになります。純粋関数(同じ入力で同じ出力、副作用なし)にだけ使います。

フィボナッチ数列の再帰実装を @lru_cache で高速化します。fib(30)程度でも素のままだとループが膨大になりますが、キャッシュ付きなら一瞬です。

① functools からlru_cacheを読み込んでください

@lru_cache(maxsize=128)を付けたfib(n)関数を定義してください(n < 2 なら n を返し、それ以外はfib(n-1) + fib(n-2)を返す)

fib(30)の結果をfib(30): ◯の形で表示してください

fib(50)の結果をfib(50): ◯の形で表示してください(キャッシュ無しだと現実的時間で終わらない値です)

fib.cache_info().maxsizeキャッシュの上限maxsize: ◯の形で表示してください

Python エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1リスト[1, 2, 3][A, B]デカルト積を作るのに最も簡潔な書き方はどれですか?

Q2partial(f, x=10)を呼び出した戻り値はどれですか?

Q3素の再帰実装のフィボナッチ@lru_cacheを付けて高速化できる理由はどれですか?