Python の内包表記の使い方まとめ

Python の内包表記についてまとめました。

内包表記とは?

内包表記とは、リストや辞書などの iterable オブジェクト( for ループで回せるオブジェクト)のループ処理をかんたん・シンプルに記述できる記法です。

たとえば、 1 から 5 までの数値を 2 乗した値を持つリストを作りたい場合は次のような式を書くことができます。

[x ** 2 for x in [1, 2, 3, 4, 5]]
# => [1, 4, 9, 16, 25]

通常の for ループと同じ for element in collection というブロックを書いて、その前に各要素の値を書いて、 [] で囲みます。この書き方が「内包表記」です。

ちなみに、「内包」は英語では「 comprehension 」と言います。また、「内包」の逆の概念は「外延」( extension )と言うそうです。内包と外延は数学の集合論における表現方法のようです。

このあたりの概念について詳しくは Wikipedia などが参考になります。

種類

内包表記の種類として次の4つがあります。

  1. リスト内包
  2. ジェネレータ式
  3. セット内包表記
  4. 辞書内包

内包表記といえば、基本は「リスト内包」かと思うのですが、「リスト内包」のほかにも「ジェネレータ式」やセット版や辞書版が存在します。

順に見ていきましょう。

リスト内包

リスト内包は [] で定義します。リストを返します。

[x + 2 for x in range(5)]
# => [2, 3, 4, 5, 6]

ジェネレータ式

ジェネレータ式はリスト内包の最初と最後の [] の部分を () に変更したものです。戻り値はリストではなく、要素をひとつずつ生成するイテレータです。

(x + 2 for x in range(5))
# => <generator object <genexpr> at 0x106017db0>

セット内包表記

セット内包は {} で定義します。 set を返します。

{x + 2 for x in range(5)}
# => {2, 3, 4, 5, 6}

辞書内法

辞書内法も {} で定義します。辞書を返します。

li = [("C", 1972), ("Java", 1995), ("JavaScript", 1995)]
{k: v for k, v in li}
# => {'C': 1972, 'Java': 1995, 'JavaScript': 1995}

セット内包との表記の違いは、各要素を key: value という形で記述する点です。

いろいろな使い方

内包表記では基本的なループの他にも多重ループやフィルタ処理なども行うことができます。

内包表記以外の機能をあわせて、いくつかのパターンをあげてみます。

基本のループ:

# 通常の 1 重ループ
# `map()` と同等
[x ** 2 for x in range(10)]

基本のループ + フィルタ:

# if 節の条件が成り立つもののみをリストに含める
# `filter()` と同等
[x ** 2 for x in range(10) if x % 2 == 0]

複数要素のループ:

# 複数のリストを同時に回す方法
[x * y for x, y in zip([1, 2, 3], [11, 12, 13])]

多重ループ:

# 独立した2つのリストを2重ループとして回す
[x + y for x in range(3) for y in [100, 200, 300]]

ネスト:

# ネストされたリストに対して 2 重ループを回す
# 順番は、外側のループ → 内側のループの順
[x for inner_list in [[1, 3], [5], [7, 9]] for x in inner_list]

条件によって値を変える:

# 値の部分に if else 式を使う
[x if x % 3 == 0 else 'fizz' for x in range(10)]

条件によって値を変えて fizzbuzz:

# きれいじゃないけど if else の入れ子も可能
[('fizzbuzz' if x % 15 == 0 else ('fizz' if x % 3 == 0 else ('buzz' if x % 5 == 0 else x))) for x in range(1, 30)]

while ループ:

# ジェネレータ式と itertools.takewhile で while 条件の指定も可能
from itertools import takewhile

takewhile(lambda x: x < 30, (n for n in range(100)))

takewhile を使わずに途中で break:

# while ループと同じことを if else 式で実現
def end_of_loop():
    raise StopIteration

list(x if x < 10 else end_of_loop() for x in range(100))
# 次の書き方でもOK
# list(x if x < 10 else next(iter([])) for x in range(100))

最後の StopIteration 例外を使った break はトリック的な小技になりますが、こういうこともできることは覚えていてもよいかもしれません。

以上です。

参考