Q1リスト[1, 2, 3]と[A, B]のデカルト積を作るのに最も簡潔な書き方はどれですか?
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 / tuple / str / dict / set / rangeなどの組み込み型に加えて、yieldを使った自作のジェネレータもイテラブルとして振る舞う。itertools — 組み合わせと反復の関数群
itertoolsはイテラブルから新しいイテラブルを作る関数を集めたモジュールです。よく使うのはcombinations(順序なしの組み合わせ)/ product(デカルト積、複数集合の全組)/ chain(複数のイテラブルを 1 本に連結)/ accumulate(累積和や累積積)の 4 つで、これらを覚えておくと二重 for ループや状態を持ったループをすっきり書き換えられます。
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] |
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円
power(base, exp)のexp=2を固定すれば、後はbaseだけ渡せばよいsquare関数になる。functools.lru_cache — メモ化で計算結果をキャッシュする
「同じ引数で何度も呼ばれる重い関数を、毎回計算せず使い回したい」 — 計算結果が決まりきっている関数で速度が問題になる場面で必要になります。普通に書くと自前でキャッシュ用の dict を管理する必要がありますが、@lru_cacheならデコレータ 1 行で済みます。
@lru_cacheは関数の戻り値をキャッシュするデコレータです。同じ引数で再度呼ばれたとき、前回の戻り値をそのまま返すので、再計算が要らなくなります。LRU(Least Recently Used、直近で使われていないものから捨てる方式)で古いキャッシュから捨てるので、maxsize=128のような上限を指定してメモリ使用量を制限できます。
副作用のある関数には付けない
@lru_cacheは「同じ引数なら同じ戻り値」という前提で動きます。ファイルを読む / DB に書く / 現在時刻を返すなど副作用のある関数や、同じ引数で結果が変わる関数に付けると、1 回目の結果をずっと返し続けるバグになります。純粋関数(同じ入力で同じ出力、副作用なし)にだけ使います。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2partial(f, x=10)を呼び出した戻り値はどれですか?
Q3素の再帰実装のフィボナッチに@lru_cacheを付けて高速化できる理由はどれですか?