Python | データ構造強化:list の内包最適化

Python Python
スポンサーリンク

概要(「内包表記」を正しく使えば“短く・速く・安全”に書ける)

リスト内包表記は、既存データから新しいリストを「1行」で作るための記法です。for と append を並べるより、処理の意図が明確で速く動くことが多いのが利点です。ただし、複雑にし過ぎると可読性や性能が落ちます。「計算を内包に寄せる」「条件の順序を最適化」「関数呼び出しや属性参照のコストを減らす」など、いくつかのコツを押さえると実務品質になります。

基本の使い方(ここが重要)

変換とフィルタを“1行に”まとめる

# 0〜9の二乗(変換)
squares = [x * x for x in range(10)]

# 0〜9から偶数だけ(フィルタ)
evens = [x for x in range(10) if x % 2 == 0]

# 変換+フィルタ(順番が重要:先にフィルタ)
evens_squared = [x * x for x in range(10) if x % 2 == 0]
Python

ポイントは「フィルタを先にかけて、変換対象を減らす」こと。不要な計算をスキップできて速くなります。

if-else は“式”として使う(分岐を内包)

labels = ["偶数" if x % 2 == 0 else "奇数" for x in range(1, 6)]
Python

内包の中では「if-else は式」。行外の if/else と混在させず、分岐を内包側に寄せると短くて読みやすいです。

ネストは読みやすさ優先(深くなったら分ける)

# 2次元の平坦化(軽いネスト)
flat = [v for row in [[1, 2], [3, 4]] for v in row]
Python

ネストが2段を超えたら関数に切り出すか、途中で変数に分けて読みやすさを保ちましょう。可読性低下は最適化の敵です。

重要ポイントの深掘り(速度・メモリ・名前参照)

内包表記が速い理由と、さらに速くするコツ

  • ループ内部の append がない: 内包は CPython 実装的にも効率的なループになります。
  • フィルタの順序最適化: 先に軽い条件で落とし、重い計算は後回しに。
  • 属性・関数参照の前取り(ルックアップの削減): ループ内で何度も参照されるものは、事前にローカル変数へ。
# 参照の前取りで高速化
import math
sine = math.sin  # ここで束縛するとループが速い
values = [sine(x) for x in range(100000)]
Python

属性やグローバルを毎回辿ると遅いので、ローカルに束縛してから使うのが定石です。

メモリ最適化(リストが不要なら“ジェネレータ”)

「逐次処理で十分」なら、リストではなくジェネレータ式にしてメモリ消費を抑えます。

# リストが不要なら () にして遅延生成
gen = (x * x for x in range(10**7))
# たとえば合計だけ必要なら
total = sum(gen)
Python

内包表記は“全部作る”のでメモリを使います。でかいデータは遅延生成に切り替えると安全です。

map/filter との使い分け(内包の方が読みやすい場面が多い)

# 同じ処理でも可読性は内包が勝ちやすい
result = [f(x) for x in data if cond(x)]
# 対して map/filter は関数形
result2 = list(map(f, filter(cond, data)))
Python

関数を渡すのが自然な場面(既に f, cond がある)なら map/filter でも良いですが、読みやすさは内包が優位です。チームのコーディング規約に合わせましょう。

実務で効く最適化パターン

前処理で“高コスト計算を減らす”(順序を意識)

records = [{"price": 120, "qty": 2}, {"price": 300, "qty": 0}]
# 先に軽いフィルタ → 次に変換
totals = [r["price"] * r["qty"] for r in records if r["qty"] > 0]
Python

軽いフィルタを先に入れると、後段の高コスト計算や I/O を減らせます。順序は性能に直結します。

ループ外で定数・関数・正規表現を束縛

import re
pat = re.compile(r"[A-Za-z]+")
tokens = [m.group(0).lower() for m in pat.finditer(text)]
Python

毎回 re.compile するのは非効率。外に出して再利用すると大幅に改善します。

重複除去や集合演算は set と組み合わせる

# 内包+集合で重複なしに整形
unique_exts = {p.suffix.lower() for p in paths if p.suffix}
# 最終的に並べたいなら
sorted_exts = sorted(unique_exts)
Python

重複が許容されないなら、最初から set 内包(波括弧)へ。目的に合ったデータ構造を選ぶのも最適化です。

enumerate と辞書内包で“意図を明確化”

# インデックスを使うなら enumerate
pairs = [(i, x) for i, x in enumerate(["a", "b", "c"], start=1)]

# 辞書が必要なら dict 内包へ
price_by_name = {r["name"]: r["price"] for r in rows if r["in_stock"]}
Python

「何を作りたいか」をデータ構造から伝えると、保守しやすくバグも減ります。

よくある落とし穴と回避策

副作用(print, 書き込み)を内包に入れない

内包は「作る」ための構文です。副作用は for で書いて目的を分離すると、動作が明確で安全です。

# 悪い例:副作用を内包で実行しない
# [print(x) for x in data]  # やめる

# 良い例:純粋に作る or 普通の for を使う
values = [f(x) for x in data]  # 生成
for x in data: print(x)        # 出力
Python

過度なネスト・複雑な条件は分割する

一行が長くなるほど読みづらく、最適化の余地も見えなくなります。段階を分け、途中結果に名前を与えるだけで理解度と保守性が上がります。

# 分けて書くと速さと可読性が両立
filtered = (x for x in items if light_check(x))
result   = [heavy(x) for x in filtered if heavy_check(x)]
Python

巨大リストの作成を避ける(遅延・ストリームへ)

「合計」「最大」「書き出し」などの集計や I/O が目的なら、作成せずに直接流す方がメモリも速度も良くなります。

# 直接流す(例:合計へ)
total = sum(x * x for x in range(10**7))
Python

例題で身につける(定番から一歩先まで)

例題1:フィルタ順序の最適化(軽い→重い)

# 軽い条件で早期除外 → 重い計算を減らす
def heavy_calc(x): return x**3 - x**2 + x  # 重い仮定
def cheap_check(x): return x % 2 == 0      # 軽い

values = [heavy_calc(x) for x in range(10000) if cheap_check(x)]
Python

例題2:属性参照をローカルへ束縛して高速化

class P:
    def __init__(self, x): self.x = x
objs = [P(i) for i in range(100000)]

getx = P.x.__get__         # 参照を束縛(または lambda o: o.x)
xs   = [getx(o) for o in objs]
Python

例題3:遅延生成で大規模データを安全に処理

# 1000万件を“合計だけ欲しい”
total = sum(x for x in range(10_000_000) if x % 3 == 0)
Python

例題4:辞書・集合内包で目的に沿うデータ構造へ

rows = [{"name": "coffee", "qty": 2}, {"name": "tea", "qty": 0}, {"name": "sugar", "qty": 3}]
in_stock = {r["name"] for r in rows if r["qty"] > 0}   # 集合内包(重複排除)
price_map = {r["name"]: r["qty"] * 100 for r in rows}  # 辞書内包(計算付き)
Python

まとめ

リスト内包表記は「変換・フィルタ」を1行で完結できる強力な基本形です。最適化の要点は、軽い条件を先にする順序設計、属性や関数参照の前取りでループを軽くすること、巨大データはジェネレータ式で遅延処理に切り替えること。副作用は内包から分離、過度なネストは分割して読みやすさを保つ。辞書・集合内包や enumerate と組み合わせて“何を作るか”を明確にすれば、短く、速く、堅牢なコードが書けます。

タイトルとURLをコピーしました