for ループを 1 行で書く リスト内包表記 と、無名関数の lambda は、Python のコードを短くする道具です。本記事では使いどころと、逆に読みづらくなる境界線を整理します。

目次

  1. リスト内包表記の基本
  2. 条件で絞り込む
  3. 三項演算子と組み合わせる
  4. 辞書内包表記・集合内包表記
  5. ジェネレータ式 — 1 度しか使わないなら
  6. 株価データでの実例
  7. lambda — 名前のない関数
  8. sorted / max などのキー関数
  9. map / filter は内包表記で代替
  10. 読みづらくなる境界線

リスト内包表記の基本

「コレクションを 1 つずつ処理して新しいリストを作る」操作は、内包表記で 1 行に書けます。

prices = [2900, 2925, 2880, 2910, 2945]
# for ループで書く場合
doubled = []
for price in prices:
doubled.append(price * 2)
# 内包表記で書く場合
doubled = [price * 2 for price in prices]
print(doubled) # [5800, 5850, 5760, 5820, 5890]

形は [式 for 変数 in イテラブル] です。最初の式が「新しい要素」、後ろが「元のループ」に対応します。

条件で絞り込む

if を後ろに付けると、条件に合うものだけを残せます。

prices = [2900, 2925, 2880, 2910, 2945]
high_prices = [p for p in prices if p > 2900]
print(high_prices) # [2925, 2910, 2945]

通常の if 文と同じく、複数条件は and / or で連結できます。

prices = [2900, 2925, 2880, 2910, 2945]
window = [p for p in prices if 2890 < p < 2930]
print(window) # [2900, 2925, 2910]

三項演算子と組み合わせる

「条件に合う場合は変換し、合わなければそのまま」という処理は三項演算子と組み合わせます。

prices = [2900, 0, 2880, 0, 2945]
cleaned = [p if p > 0 else None for p in prices]
print(cleaned) # [2900, None, 2880, None, 2945]

ここでの if式の中の三項演算子 で、絞り込みの if とは別物です。両方を使うときは順番に注意します。

prices = [2900, 0, 2880, 0, 2945]
# 三項演算子は前、絞り込みの if は後ろ
cleaned = [p if p > 0 else 0 for p in prices if p is not None]

辞書内包表記・集合内包表記

{ } を使うと、辞書や集合も同じ書き方で作れます。

prices = [2900, 2925, 2880]
indexed = {i: p for i, p in enumerate(prices)}
print(indexed) # {0: 2900, 1: 2925, 2: 2880}
tickers = ["7203", "9984", "7203", "6758"]
unique = {t for t in tickers}
print(unique) # {'7203', '9984', '6758'}

辞書内包表記は キーで引きたい対応付け を作るのに、集合内包表記は 重複を排除する のに向いています。

ジェネレータ式 — 1 度しか使わないなら

( ) で囲むと、要素を 1 つずつ生成するジェネレータになります。リストを丸ごと作らないため、メモリに優しい書き方です。

prices = [2900, 2925, 2880, 2910, 2945]
total = sum(p * 2 for p in prices) # リストを作らずに合計
print(total) # 29320

sum / max / any / all のように「1 回だけ走らせれば良い」関数と組み合わせると効果的です。

株価データでの実例

日次リターンを内包表記で書く例です。

prices = [100, 102, 101, 103, 105, 107]
returns = [
(curr - prev) / prev
for prev, curr in zip(prices[:-1], prices[1:])
]
print(returns)
# [0.02, -0.0098..., 0.0198..., 0.0194..., 0.0190...]

zip で前日と当日のペアを作り、その上で計算する形は時系列処理の定石です。

lambda — 名前のない関数

lambda は、その場限りの小さな関数を作る記法です。

square = lambda x: x ** 2
print(square(5)) # 25

ただし、lambda を変数に代入するくらいなら、def で関数を定義するほうが読みやすくなります。lambda が真価を発揮するのは、関数を引数として渡す 場面です。

sorted / max などのキー関数

sortedmaxkey= に関数を渡すと、その値で並べ替え・比較を行います。

stocks = [
{"ticker": "7203", "price": 2900},
{"ticker": "9984", "price": 9800},
{"ticker": "6758", "price": 12500},
]
by_price = sorted(stocks, key=lambda s: s["price"])
top = max(stocks, key=lambda s: s["price"])
print(by_price[0]) # {'ticker': '7203', 'price': 2900}
print(top) # {'ticker': '6758', 'price': 12500}

並べ替えのキーをその場で定義したいときに lambda がよく合います。

map / filter は内包表記で代替

関数型の map / filterlambda と組み合わせて使えますが、Python では 内包表記のほうが慣例 です。

prices = [2900, 2925, 2880]
# map + lambda
doubled = list(map(lambda p: p * 2, prices))
# 内包表記(こちらが推奨)
doubled = [p * 2 for p in prices]

書きやすさが大きく変わるわけではありませんが、Python のコミュニティでは内包表記のほうが読みやすいと考えられています。

読みづらくなる境界線

内包表記と lambda は便利ですが、行きすぎると読みづらくなります。次のサインが出たら通常のループや関数に戻すのが無難です。

  • 複数の for をネストしている(2 重の内包表記)
  • 三項演算子と絞り込み if の両方を入れている
  • 1 行が 100 文字を超える
  • 関数の中身が複雑で、lambda の本体が長くなる

たとえば次のような書き方は、for ループで 5 行に展開したほうが読みやすくなります。

# ❌ 読みづらい
result = [
(t, p * 2 if p > 0 else 0)
for t, p in pairs
for p in [p1, p2, p3]
if t.startswith("7")
]

「短く書ける」と「読みやすい」は別の問題です。コードの読み手(将来の自分や同僚)が一読で意図を取れる範囲に収めます。

生成AI へのプロンプト例

内包表記の書き換えを生成AI に依頼する例です。

次の Python コードを、可読性を保てる範囲でリスト内包表記に
書き換えてください。書き換えが可読性を下げるなら、その理由と
ともに「書き換えない」を選んでください。
prices = [100, 102, 101, 103, 105]
returns = []
for i in range(1, len(prices)):
prev = prices[i - 1]
curr = prices[i]
if prev > 0:
returns.append((curr - prev) / prev)

「書き換えない選択肢」を残すと、無理な圧縮を避ける応答が返ります(#7-2「データ分析のためのプロンプト設計」)。

まとめ

  • リスト内包表記は [式 for 変数 in iterable if 条件] の形で短く書ける
  • 辞書・集合・ジェネレータも同じ記法で作れる
  • lambda は関数を引数で渡す場面(sorted / maxkey=)で活きる
  • 内包表記が長くなったり多重になったら、通常のループに戻すほうが読みやすい