3.6 内包表記、ジェネレーター式
Pythonの「内包表記(Comprehensions)」は、既存のリストや辞書などのデータ(イテラブル)から、新しいデータコレクションを一行で簡潔に作成するための強力な構文です。
通常の for ループを使って空のリストに要素を追加していく処理に比べ、内包表記を使うとコードの行数が減り、実行速度も向上することが多いというメリットがあります。
本記事では、「3.6 内包表記、ジェネレーター式」として、各種内包表記の基本からメモリ効率に優れたジェネレーター式までを解説します。
1. リスト内包表記 (List Comprehensions)
Section titled “1. リスト内包表記 (List Comprehensions)”最もよく使われるのが、新しいリストを生成する「リスト内包表記」です。角括弧 [] を使用します。
1.1 基本的な構文
Section titled “1.1 基本的な構文”[式 for 変数 in イテラブル]【従来の for ループとの比較】 たとえば、数値のリストからそれぞれの値を2倍にした新しいリストを作りたい場合を比較してみましょう。
numbers = [1, 2, 3, 4, 5]
# forループを使う場合(従来の方法)doubled = []for x in numbers: doubled.append(x * 2)print(doubled) # [2, 4, 6, 8, 10]
# リスト内包表記を使う場合doubled_comp = [x * 2 for x in numbers]print(doubled_comp) # [2, 4, 6, 8, 10]1.2 条件付きのリスト内包表記
Section titled “1.2 条件付きのリスト内包表記”後ろに if 文を追加することで、条件を満たす要素だけを抽出して新しいリストを作ることができます。
[式 for 変数 in イテラブル if 条件式]# 偶数だけを取り出して2乗するevens_squared = [x**2 for x in numbers if x % 2 == 0]print(evens_squared) # [4, 16] (2と4の2乗)2. 辞書と集合の内包表記
Section titled “2. 辞書と集合の内包表記”リストだけでなく、辞書(dict)や集合(set)も内包表記で生成できます。波括弧 {} を使用します。
2.1 辞書内包表記 (Dictionary Comprehensions)
Section titled “2.1 辞書内包表記 (Dictionary Comprehensions)”キーと値をコロン : で区切って指定します。
# {キーの式: 値の式 for 変数 in イテラブル}
words = ["apple", "banana", "cherry"]# 単語をキー、その文字数を値とする辞書を作成word_lengths = {word: len(word) for word in words}print(word_lengths)# {'apple': 5, 'banana': 6, 'cherry': 6}2.2 集合内包表記 (Set Comprehensions)
Section titled “2.2 集合内包表記 (Set Comprehensions)”コロンを含めずに式だけを書くと、重複を許さない集合(セット)が生成されます。
# {式 for 変数 in イテラブル}
data = [1, 2, 2, 3, 4, 4, 5]# 重複を排除して偶数だけの集合を作成even_set = {x for x in data if x % 2 == 0}print(even_set)# {2, 4}3. ジェネレーター式 (Generator Expressions)
Section titled “3. ジェネレーター式 (Generator Expressions)”丸括弧 () を使用すると、リストや辞書ではなく「ジェネレーターオブジェクト」が生成されます。
# (式 for 変数 in イテラブル)
gen = (x**2 for x in range(1000000))print(gen)# <generator object <genexpr> at 0x...>リスト内包表記とジェネレーター式の違い(遅延評価)
Section titled “リスト内包表記とジェネレーター式の違い(遅延評価)”リスト内包表記は、実行された瞬間にすべての要素をメモリ上に生成します。そのため、データ量が膨大な場合はメモリ不足(メモリ枯渇)を引き起こす可能性があります。
一方、ジェネレーター式は「要素を要求されたときに初めて、その次の1つだけを生成して返す」という遅延評価(Lazy Evaluation)を行います。そのため、どれだけ対象データが大きくても、消費するメモリはごくわずかで済みます。
import sys
# 100万個の要素を持つリスト(メモリを大量に消費)list_comp = [x for x in range(1000000)]print(sys.getsizeof(list_comp)) # 例: 約 8,000,000 バイト
# 100万個の要素を生成するジェネレーター(メモリをほとんど消費しない)gen_expr = (x for x in range(1000000))print(sys.getsizeof(gen_expr)) # 例: 約 104 バイトすべてのデータを一度にリストとして保持する必要がない場合(単に for ループで順番に処理したいだけの場合など)は、ジェネレーター式を使うのがベストプラクティスです。
4. 内包表記の注意点:やりすぎ厳禁
Section titled “4. 内包表記の注意点:やりすぎ厳禁”内包表記は便利ですが、ループを何重にもネストしたり、複雑な条件式を詰め込みすぎると、かえって可読性が著しく低下します。
# 悪い例:複雑すぎて何をしているか一目でわからないresult = [x * y for x in range(10) if x % 2 == 0 for y in range(5) if y != x]「パッと見て何をしているか理解できない」レベルになったら、無理に一行にまとめず、素直に通常の for 文と if 文を展開して書くようにしましょう。