1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonの内包表記 — 最初は読めなかったけど慣れたら手放せなくなった

1
Posted at

はじめに

Pythonのコードを読み始めたとき、こういう行に何度も出くわした。

result = [x * 2 for x in nums if x % 2 == 0]

PHPしか書いてこなかった自分には最初「なんだこの呪文は」だった。

でも慣れたら手放せなくなった。array_map()array_filter()をネストして書いていたものが1行で読みやすく書ける。今では積極的に使っている。


リスト内包表記

基本形はこう。

[式 for 変数 in イテラブル]

PHPのarray_map()に相当する。

<?php
$nums = [1, 2, 3, 4, 5];

// array_mapで2倍
$doubled = array_map(fn($x) => $x * 2, $nums);
nums = [1, 2, 3, 4, 5]

# 内包表記で2倍
doubled = [x * 2 for x in nums]
print(doubled)  # [2, 4, 6, 8, 10]

条件フィルターつき

[式 for 変数 in イテラブル if 条件]

PHPのarray_filter()に相当する。

<?php
// 偶数だけ取り出す
$evens = array_filter($nums, fn($x) => $x % 2 === 0);
# 偶数だけ取り出す
evens = [x for x in nums if x % 2 == 0]
print(evens)  # [2, 4]

map + filterを同時にやる

PHPだとarray_map()array_filter()を組み合わせるとネストが深くなる。

<?php
// 偶数だけ取り出して2倍
$result = array_map(
    fn($x) => $x * 2,
    array_filter($nums, fn($x) => $x % 2 === 0)
);
# 内包表記なら1行
result = [x * 2 for x in nums if x % 2 == 0]
print(result)  # [4, 8]

読む順番が「何を → どこから → どれだけ」と自然な語順になっているのが内包表記のいいところ。


辞書内包表記

辞書も内包表記で作れる。

{キー式: 値式 for 変数 in イテラブル}
fruits = ["apple", "banana", "cherry"]

# 文字列とその長さの辞書を作る
length_map = {fruit: len(fruit) for fruit in fruits}
print(length_map)
# {'apple': 5, 'banana': 6, 'cherry': 6}

PHPだとarray_combine()array_reduce()、あるいはforeachでループして詰める。

<?php
$fruits = ["apple", "banana", "cherry"];
$length_map = [];
foreach ($fruits as $fruit) {
    $length_map[$fruit] = strlen($fruit);
}

辞書のキーと値を入れ替える

original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted)  # {1: 'a', 2: 'b', 3: 'c'}

これをPHPでやるとarray_flip()だが、条件つきで入れ替えたい場合はPythonの辞書内包表記のほうが柔軟に書ける。

条件フィルターつき

scores = {"田中": 85, "鈴木": 42, "佐藤": 91, "伊藤": 38}

# 60点以上だけ取り出す
passing = {name: score for name, score in scores.items() if score >= 60}
print(passing)  # {'田中': 85, '佐藤': 91}

セット内包表記

words = ["apple", "banana", "apple", "cherry", "banana"]

# 重複を除いた単語の文字数セット
unique_lengths = {len(word) for word in words}
print(unique_lengths)  # {5, 6}

波括弧を使うが辞書内包表記と違いキー: 値形式ではなく値だけを書く。使う頻度はリスト・辞書と比べて低いが、知っておくと便利。


ネストした内包表記

2重ループも内包表記で書ける。

# 2次元リストをフラットにする
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

読む順番はforを左から右に読んでいく。

# 外側のループ → 内側のループ
[x for row in matrix for x in row]
#   ↑ 外           ↑ 内

九九の表を作る

kuku = [[i * j for j in range(1, 10)] for i in range(1, 10)]
print(kuku[2])  # [3, 6, 9, 12, 15, 18, 21, 24, 27](3の段)

ただしネストが深くなると一気に読みにくくなる。2重ネストまでを限度にしておくのが個人的な基準。3重以上は素直にforループを使う。


ジェネレーター式

内包表記に似ているが、[]の代わりに()で囲む。

# リスト内包表記 → 全部メモリに展開する
squares_list = [x ** 2 for x in range(1000000)]

# ジェネレーター式 → 必要なときだけ計算する
squares_gen = (x ** 2 for x in range(1000000))

ジェネレーター式はリストを一気に作らず、要素を1つずつ遅延評価する。大量のデータを扱うとき、リスト内包表記だとメモリを大量消費してしまう。

# sum()やmax()に直接渡せる(リストを作らない)
total = sum(x ** 2 for x in range(1000000))
print(total)

データ処理案件では大きいファイルや大量レコードを扱う場面が出てくるので、この違いは意識しておきたい。


内包表記を使うべきでないケース

便利だからといって何でも内包表記にするのは良くない。

# 読みにくい内包表記(やりすぎ)
result = [
    {"name": user["name"], "score": user["score"] * 1.1}
    for user in users
    if user["active"]
    if user["score"] >= 60
    if user["role"] != "admin"
]

条件が3つ以上になったり、式が複雑になったりしたら素直にforループで書く。

# こっちのほうが読みやすい
result = []
for user in users:
    if not user["active"]:
        continue
    if user["score"] < 60:
        continue
    if user["role"] == "admin":
        continue
    result.append({
        "name": user["name"],
        "score": user["score"] * 1.1,
    })

内包表記は「シンプルなmapとfilterの組み合わせ」に留めるのが読みやすさのラインだと思っている。


まとめ

種類 構文 PHPの対応
リスト内包表記 [式 for x in ...] array_map()
フィルターつき [式 for x in ... if 条件] array_filter() + array_map()
辞書内包表記 {k: v for x in ...} foreachでループして詰める
セット内包表記 {式 for x in ...} array_unique() + array_map()
ジェネレーター式 (式 for x in ...) 相当なし(yieldは近い)

内包表記に慣れるとarray_map(array_filter(...))のネストが懐かしくなってくる。最初の「読めない」という壁を超えれば、PHPより自然に書けると感じる部分の一つ。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?