Pythonの内包表記とは、[式 for 変数 in イテラブル if 条件] の1行で、新しいリスト・辞書・集合を作る書き方です。 for文と append で4行かかる処理が1行になり、多くの場合は実行も少し速くなります。

この記事では、リスト・辞書・集合・ジェネレータ式の書き方を1枚の早見表にまとめ、迷いやすい if と if-else の位置の違い、二重ループの順番、zip・enumerate・セイウチ演算子 := との組み合わせをすべて実行結果つきで示します。さらに、for+append・map・filterとの速度を timeitでPython 3.11と3.14の両方で計測し、3.12で入った「内包表記のインライン化」がどう効くかを確かめます。最後に、ループで書いたほうが読みやすい場面の判断基準と、実際のエラー文を紹介します。

検証環境:Linux、Python 3.11.15/Python 3.14.7。

内包表記の早見表

種類 書き方 実行結果(nums = [1, 2, 3, 4, 5, 6])
リスト [n * 10 for n in nums] [10, 20, 30, 40, 50, 60]
リスト+絞り込み [n for n in nums if n % 2 == 0] [2, 4, 6]
リスト+if-else ["偶数" if n % 2 == 0 else "奇数" for n in nums] ['奇数', '偶数', '奇数', '偶数', '奇数', '偶数']
if-else+絞り込み [n * 10 if n > 3 else n for n in nums if n != 5] [1, 2, 3, 40, 60]
辞書 {name: price for name, price in zip(names, prices)} {'ノート': 120, 'ペン': 80, '付箋': 200}
辞書+絞り込み {name: p for name, p in zip(names, prices) if p >= 100} {'ノート': 120, '付箋': 200}
集合 {n % 3 for n in nums} {0, 1, 2}
ジェネレータ式 sum(n * n for n in nums) 91
タプルを作る tuple(n for n in nums if n > 4) (5, 6)
番号つき [f"{i}:{name}" for i, name in enumerate(names, start=1)] ['1:ノート', '2:ペン', '3:付箋']
二重ループ [(r, c) for r in range(2) for c in "ab"] [(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')]
平らにする [x for row in matrix for x in row] [1, 2, 3, 4, 5, 6]
行列の転置 [[row[i] for row in matrix] for i in range(3)] [[1, 4], [2, 5], [3, 6]]
辞書のキーと値を入れ替え {v: k for k, v in stock.items()} {5: 'ノート', 0: 'ペン', 12: '付箋'}
辞書を絞り込む {k: v for k, v in stock.items() if v > 0} {'ノート': 5, '付箋': 12}
計算結果で絞る(3.8+) [y for x in nums if (y := x * x) > 10] [16, 25, 36]

前提データ:names = ["ノート", "ペン", "付箋"]、prices = [120, 80, 200]、matrix = [[1, 2, 3], [4, 5, 6]]、stock = {"ノート": 5, "ペン": 0, "付箋": 12}。

覚え方 内容
かっこで種類が決まる [] リスト、{k: v} 辞書、{x} 集合、() ジェネレータ式(タプル内包表記はない)
if だけなら後ろ 絞り込み(要素を減らす)
if-else なら前 値の切り替え(要素数は変わらない)
for は外側から順に書く 普通の入れ子のforと同じ順番

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

(記載例)出典:株式会社RJC「Python内包表記|リスト・辞書・集合の書き方と速度比較一覧」

リスト内包表記の基本形

[式 for 変数 in イテラブル] は、「イテラブルから1つずつ取り出して、式の結果を集める」という意味です。 次の2つは同じリストを作ります。

# Python 3.8+
nums = [1, 2, 3, 4, 5, 6]

# for文で書く
out = []
for n in nums:
    out.append(n * 10)
print(out)

# 内包表記で書く
print([n * 10 for n in nums])

実行結果:

[10, 20, 30, 40, 50, 60]
[10, 20, 30, 40, 50, 60]

文字列の整形のように、メソッドを続けて呼ぶ処理とも相性がよい書き方です。

print([w.strip().lower() for w in [" Apple", "BANANA "]])
print([c for c in "a1b2c3" if c.isdigit()])
['apple', 'banana']
['1', '2', '3']

ifとif-elseの位置の違い

if だけ(絞り込み)は for の後ろ、if-else(値の切り替え)は for の前に書きます。 前に書く A if 条件 else B は「条件式(三項演算子)」という1つの式なので、式を書く場所に置きます。

flowchart LR
    A["[ 式 ]"] --> B["for 変数 in イテラブル"] --> C["if 条件"]
    A -.- D["式の部分に A if 条件 else B を書ける"]
    C -.- E["ここは絞り込み専用(else は書けない)"]
やりたいこと 書き方 要素数
条件に合うものだけ残す [n for n in nums if n > 3] 減る
条件で値を変える [n if n > 3 else 0 for n in nums] 同じ
両方 [n * 10 if n > 3 else n for n in nums if n != 5] 減る
条件を2つ以上で絞る [n for n in nums if n > 1 if n < 5](and と同じ) 減る

位置を取り違えたときのエラー

[n for n in range(5) if n % 2 == 0 else 0]
  File "<string>", line 1
    [n for n in range(5) if n % 2 == 0 else 0]
                                       ^^^^
SyntaxError: invalid syntax
[n if n > 2 for n in range(5)]
SyntaxError: expected 'else' after 'if' expression

どちらも3.11・3.14で同じメッセージでした。後ろの if に else は書けず、前の if には else が必要、と覚えておくと迷いません。

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

辞書内包表記は {キー: 値 for ...}、集合内包表記は {値 for ...} です。 コロンの有無で区別されます。

# Python 3.8+
names = ["ノート", "ペン", "付箋"]
prices = [120, 80, 200]
stock = {"ノート": 5, "ペン": 0, "付箋": 12}

print({name: price for name, price in zip(names, prices)})
print({k: v for k, v in stock.items() if v > 0})
print({v: k for k, v in stock.items()})
print({k: sorted(v) for k, v in {"a": [3, 1], "b": [2]}.items()})
print({len(name) for name in names})

実行結果(Python 3.14.7):

{'ノート': 120, 'ペン': 80, '付箋': 200}
{'ノート': 5, '付箋': 12}
{5: 'ノート', 0: 'ペン', 12: '付箋'}
{'a': [1, 3], 'b': [2]}
{2, 3}
やりたいこと 内包表記 内包表記以外の書き方
2つのリストから辞書 {k: v for k, v in zip(ks, vs)} dict(zip(ks, vs))(変換しないならこちらが短い)
全キーに同じ初期値 {k: 0 for k in ks} dict.fromkeys(ks, 0)
値を変換 {k: v * 1.1 for k, v in d.items()} —
重複を除いた値の一覧 {x.lower() for x in words} set(map(str.lower, words))

dict.fromkeys(ks, []) のように変更できる値を初期値にすると、全キーが同じリストを共有します。 キーごとに別のリストが欲しいときは {k: [] for k in ks} と内包表記で書きます。辞書そのものの操作は、別記事「Python辞書(dict)の使い方」で詳しく扱っています。

ジェネレータ式

ジェネレータ式は (式 for ...) で、値を1つずつ作りながら渡す書き方です。 リストを丸ごと作らないため、sum・max・any・"".join などに渡すときにメモリを節約できます。

import sys
a = [n for n in range(1000)]
g = (n for n in range(1000))
print(sys.getsizeof(a), sys.getsizeof(g))
print(sum(n * n for n in range(1, 7)))      # 関数の唯一の引数ならかっこを省略できる
print(any(n > 5 for n in [1, 3, 7]))

実行結果(Python 3.14.7):

8856 200
91
True

1000要素のリストは8,856バイト、ジェネレータ式は要素数に関係なく200バイトでした(sys.getsizeof はオブジェクト本体の大きさ)。ジェネレータの詳しい仕組みは、別記事「Pythonジェネレータとyield」で解説しています。

二重ループ・zip・enumerate・セイウチ演算子

二重ループは「外側のforから順に」書く

内包表記の for は、普通の入れ子のfor文を上から順に1行に並べた順番です。

matrix = [[1, 2, 3], [4, 5, 6]]

# for文
out = []
for row in matrix:        # 外側
    for x in row:         # 内側
        out.append(x)

# 内包表記(同じ順番で並べる)
print([x for row in matrix for x in row])
[1, 2, 3, 4, 5, 6]

順番を逆にすると、まだ定義されていない変数を参照することになります。

[x for x in row for row in [[1, 2], [3, 4]]]
NameError: name 'row' is not defined. Did you mean: 'pow'?

「内包表記の中に内包表記」([[...] for ...])は、結果も入れ子のリストになります。転置の例 [[row[i] for row in matrix] for i in range(3)] は、外側の for i が先に回り、内側のリストを1つずつ作ります。

zip・enumerate と組み合わせる

names = ["ノート", "ペン", "付箋"]
prices = [120, 80, 200]
print([f"{n}={p}円" for n, p in zip(names, prices)])
print([f"{i}:{n}" for i, n in enumerate(names, start=1)])
print({n: i for i, n in enumerate(names)})
['ノート=120円', 'ペン=80円', '付箋=200円']
['1:ノート', '2:ペン', '3:付箋']
{'ノート': 0, 'ペン': 1, '付箋': 2}

セイウチ演算子 := で計算を1回にする(3.8+)

(y := 式) で、条件判定に使った計算結果をそのまま値として使えます。 重い関数を2回呼ばずに済みます。

nums = [1, 2, 3, 4, 5, 6]
print([y for x in nums if (y := x * x) > 10])
print([(last := n) for n in range(3)], last)
[16, 25, 36]
[0, 1, 2] 2

2行目のとおり、:= で代入した変数は内包表記の外側に残ります(ループ変数とは扱いが違います)。合計を途中経過として残したいときなどに使えますが、読み手が驚きやすいので必要な場面に絞るとよいでしょう。

for・map・filterとの速度比較(3.11と3.14)

10万件ではリスト内包表記がfor+appendより約1割速く、map・filterにlambdaを渡す書き方より約4割速くなりました。 次のコードで、各関数を交互に8巡実行し、最小値をとりました。

# bench.py(Python 3.8+)
import sys, timeit

def loop_append(data):
    out = []
    for x in data:
        out.append(x * 2)
    return out

def map_lambda(data):
    return list(map(lambda x: x * 2, data))

def listcomp(data):
    return [x * 2 for x in data]

def loop_filter(data):
    out = []
    for x in data:
        if x % 2 == 0:
            out.append(x)
    return out

def filter_lambda(data):
    return list(filter(lambda x: x % 2 == 0, data))

def listcomp_if(data):
    return [x for x in data if x % 2 == 0]

def loop_dict(data):
    out = {}
    for x in data:
        out[x] = x * 2
    return out

def dictcomp(data):
    return {x: x * 2 for x in data}

def map_str(data):
    return list(map(str, data))

def listcomp_str(data):
    return [str(x) for x in data]

big = list(range(100_000))
small = list(range(10))
funcs = [loop_append, map_lambda, listcomp, loop_filter, filter_lambda,
         listcomp_if, loop_dict, dictcomp, map_str, listcomp_str]
res = {f.__name__: [float("inf"), float("inf")] for f in funcs}
for _ in range(8):
    for f in funcs:
        b = min(timeit.repeat(lambda: f(big), number=20, repeat=3)) / 20
        s = min(timeit.repeat(lambda: f(small), number=50_000, repeat=3)) / 50_000
        res[f.__name__] = [min(res[f.__name__][0], b), min(res[f.__name__][1], s)]
print("Python", sys.version.split()[0])
for name, (b, s) in res.items():
    print(f"{name:<14} 10万件 {b*1e3:6.2f} ms   10件 {s*1e9:6.0f} ns")

計測結果(他の処理とCPUを共有する環境のため、数%のぶれがあります):

処理 書き方 10万件 3.11 10万件 3.14 10件 3.11 10件 3.14
2倍する for+append 2.83 ms 3.03 ms 269 ns 262 ns
map+lambda 4.25 ms 4.34 ms 541 ns 535 ns
リスト内包表記 2.57 ms 2.69 ms 302 ns 231 ns
偶数だけ for+if+append 2.68 ms 2.67 ms 360 ns 343 ns
filter+lambda 4.23 ms 3.97 ms 660 ns 599 ns
内包表記+if 2.36 ms 2.56 ms 393 ns 317 ns
辞書を作る for で代入 4.73 ms 4.75 ms 399 ns 322 ns
辞書内包表記 4.77 ms 4.61 ms 464 ns 317 ns
文字列に変換 map(str, …) 6.77 ms 5.69 ms 648 ns 597 ns
[str(x) for x in …] 6.26 ms 6.06 ms 626 ns 547 ns

表から読み取れるポイントです。

  • lambdaを使うなら内包表記のほうが速い:map・filterは要素ごとにlambda(Python関数)を呼ぶためです。
  • 組み込み関数をそのまま渡すならmapも互角:map(str, data) は3.14では内包表記より速い結果でした。
  • 10万件での差は1割前後:速度だけを理由に書き換えるほどの差ではなく、読みやすさで選んでよい範囲です。
  • 3.11では10件のような小さいリストで内包表記のほうが遅い:3.11までは内包表記を呼ぶたびに内部で関数を1つ作って呼んでいたためです。

3.12のインライン化(PEP 709)で小さいリストが速くなった

Python 3.12から、リスト・辞書・集合の内包表記は関数として作られず、その場に展開(インライン化)されるようになりました(PEP 709)。 バイトコードで確かめます。

# inline.py
import dis, sys

def f(data):
    return [x * 2 for x in data]

names = [i.opname for i in dis.get_instructions(f)]
print(sys.version.split()[0], "MAKE_FUNCTION" in names, "LOAD_FAST_AND_CLEAR" in names)
3.11.15 True False
3.14.7 False True

3.11は内包表記のために MAKE_FUNCTION(関数を作る命令)を使い、3.14では使っていません。その結果、上の表の 10件のケースで、内包表記は3.11の302 nsから3.14では231 nsに短縮され、for+append(262 ns)を上回りました。ジェネレータ式はインライン化の対象外です。

インライン化はエラー表示にも表れます。3.11のトレースバックには内包表記用の in <listcomp> という行が出ていましたが、3.14では関数 g の行だけになります(g() を呼んだ行とファイルのパスは省略)。

# Python 3.11.15
  File "inline.py", line 7, in g
    return [1 / x for x in [1, 0]]
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "inline.py", line 7, in <listcomp>
    return [1 / x for x in [1, 0]]
            ~~^~~
ZeroDivisionError: division by zero

# Python 3.14.7
  File "inline.py", line 7, in g
    return [1 / x for x in [1, 0]]
            ~~^~~
ZeroDivisionError: division by zero

スコープ:ループ変数は外に漏れない

内包表記のループ変数は内包表記の中だけで有効で、外側の同じ名前の変数を上書きしません。 for文とは違う点です。インライン化された3.12以降も、この動作は変わりません。

x = "外側"
squares = [x * 2 for x in range(3)]
print("内包表記のあと x =", x)

for x in range(3):
    pass
print("forループのあと x =", x)

g = [y for y in range(3)]
try:
    print(y)
except NameError as e:
    print("NameError:", e)

実行結果(3.11.15・3.14.7とも同じ):

内包表記のあと x = 外側
forループのあと x = 2
NameError: name 'y' is not defined

クラスの中ではクラス変数が見えない

クラス本体に書いた内包表記からは、for の最初のイテラブル以外の場所でクラス変数を参照できません。

class Shelf:
    RATE = 2
    items = [1, 2, 3]
    try:
        doubled = [x * RATE for x in items]
    except NameError as e:
        print("NameError:", e)
NameError: name 'RATE' is not defined

3.11・3.14とも同じ結果です(items は最初のイテラブルなので読めます)。メソッドの中で計算するか、最初のイテラブルにまとめて渡すと動きます。

from itertools import repeat

class Shelf:
    RATE = 2
    items = [1, 2, 3]
    doubled = [x * r for x, r in zip(items, repeat(RATE))]   # zip(...) はクラスの中で評価される

print(Shelf.doubled)
[2, 4, 6]

なお、[x * r for r in [RATE] for x in items] のように2つ目の for でクラス変数を読むと、今度は NameError: name 'items' is not defined になりました(3.11・3.14とも)。

内包表記にするか、ループで書くかの判断基準

「1行で読めて、副作用がない」なら内包表記、それ以外はfor文を目安にすると読みやすいコードになります。

状況 おすすめ 理由
変換・絞り込みをして新しいリストを作る 内包表記 意図が1行で伝わる
for が3つ以上、または条件が3つ以上 for文 読む順番を追いにくくなる
print・ファイル書き込み・DB更新が目的 for文 内包表記は「値を作る」ための書き方
try/exceptが必要 for文、または変換を関数に切り出す 内包表記の中にtryは書けない
途中で break したい for文、または next(...)・any(...) 内包表記にbreakはない
合計・最大・存在確認 ジェネレータ式を sum・max・any に渡す リストを作らずに済む
結果を1回だけ順に使う大量データ ジェネレータ式 メモリを節約できる

読みにくくなる例と書き直し

# 直す前:条件と変換が混ざって読みにくい
orders = [{"id": 1, "qty": 3, "status": "ok"}, {"id": 2, "qty": 0, "status": "ok"},
          {"id": 3, "qty": 5, "status": "hold"}]
result = [o["id"] * 100 + (o["qty"] if o["qty"] > 0 else -1) for o in orders
          if o["status"] == "ok" if o["id"] != 99]
print(result)
# 直した後:変換を関数に切り出して、内包表記は「何を集めるか」だけにする
def order_code(o):
    qty = o["qty"] if o["qty"] > 0 else -1
    return o["id"] * 100 + qty

def is_target(o):
    return o["status"] == "ok" and o["id"] != 99

print([order_code(o) for o in orders if is_target(o)])

実行結果(どちらも同じ):

[103, 199]

副作用だけが目的の内包表記(例:[print(x) for x in items])は、使わない None のリストが作られます。表示や書き込みはfor文で書きましょう。

よくあるエラーとつまずき

ジェネレータ式は1回しか使えない

ジェネレータ式は一度最後まで読むと空になります。 2回目はエラーにならず、空の結果が返る点に気をつけます。

gen = (n * 2 for n in range(3))
print("1回目:", list(gen))
print("2回目:", list(gen))
print("sum:", sum(gen))
1回目: [0, 2, 4]
2回目: []
sum: 0

何度も使うならリスト内包表記にするか、使うたびにジェネレータ式を作り直します。また、ジェネレータ式には len() が使えません(TypeError: object of type 'generator' has no len())。

エラー文の早見表

書いたコード エラー文(3.11・3.14共通) 直し方
[n for n in r if c else 0] SyntaxError: invalid syntax if-elseは前へ:[n if c else 0 for n in r]
[n if n > 2 for n in r] SyntaxError: expected 'else' after 'if' expression elseを足すか、ifを後ろへ
[x, x * 2 for x in r] SyntaxError: did you forget parentheses around the comprehension target? タプルはかっこで:[(x, x * 2) for x in r]
[x for x in row for row in m] NameError: name 'row' is not defined 外側のforを先に書く
{k: v for k, v in [1, 2]} TypeError: cannot unpack non-iterable int object 要素が2つ組か確認(d.items() の付け忘れなど)
len(n for n in r) TypeError: object of type 'generator' has no len() sum(1 for ...) か、リストにする

辞書のキーが重なると後の値で上書きされる

print({len(w): w for w in ["ペン", "ノート", "付箋"]})
{2: '付箋', 3: 'ノート'}

同じキーが2回出ると、後に出た値が残ります。 すべて残したいときは、for文と setdefault や collections.defaultdict(list) でグループにまとめます。

よくある質問

Q. タプル内包表記はありますか? A. ありません。(x for x in ...) はジェネレータ式になります。タプルが欲しいときは tuple(x for x in ...) と書きます。

Q. 内包表記はfor文より必ず速いですか? A. 今回の計測では10万件で約1割速い程度で、3.11の小さいリストではfor+appendのほうが速い結果でした。速さより「1行で意図が伝わるか」で選ぶのがおすすめです。

Q. 内包表記の中でtry/exceptは使えますか? A. 書けません。例外処理を含む変換を関数に切り出し、内包表記からその関数を呼ぶと、読みやすさも保てます。

Q. 内包表記をデバッグしにくいです。 A. いったんfor文に展開して print を入れるか、式の部分を関数にしてその中で確認します。3.12以降はトレースバックに内包表記の行がそのまま表示されるため、エラー位置は追いやすくなっています。

まとめ

  • かっこで種類が決まる:[] リスト、{k: v} 辞書、{x} 集合、() ジェネレータ式。
  • if だけなら後ろ(絞り込み)、if-else なら前(値の切り替え)。
  • 二重ループは外側のforから順に書く。順番を逆にすると NameError になります。
  • 速度は10万件でfor+appendより約1割、map+lambdaより約4割速い結果でした。
  • 3.12のインライン化で、小さいリストの内包表記が速くなりました(10件で302 ns→231 ns)。
  • ループ変数は外に漏れない。ただし := で代入した変数は外に残ります。
  • 3つ以上のforや副作用が目的の処理は、for文や関数への切り出しで読みやすくします。

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

(記載例)出典:株式会社RJC「Python内包表記|リスト・辞書・集合の書き方と速度比較一覧」

株式会社RJCでは、Pythonの読みやすい書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社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で一緒に開発しながら、 成長を楽しみませんか?