2014/09/16

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 などが参考になります。

内包と外延 - 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>

PEP 289 -- Generator Expressions | Python.org


セット内包表記

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

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


辞書内法

辞書内法も {} で定義します。辞書を返してくれます。セット内包とのちがいは、各要素の値の部分が key: value という形になっている店です。

li = [("C", 1972), ("Java", 1995), ("JavaScript", 1995)]
{k: v for k, v in li}
# => {'C': 1972, 'Java': 1995, 'JavaScript': 1995}
要素を定義する部分が「k: v」となっている点がセット内包表記とは異なります。

次に、使い方のパターンをいくつか見てみます。


いろいろな使い方

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

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

基本のループ:
# 通常の1重ループ  map のようなもの
[x ** 2 for x in range(10)]

基本のループ + フィルタ:
# if 節の条件が成り立つもののみをリストに含める filter のようなもの
[x ** 2 for x in range(10) if x % 2 == 0]

複数要素のループ:
# 複数のリストを同時に回す方法  zip を使う
[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 なんかはちょっと飛び道具というかトリック的小技になりますが、こういうこともできる、ということは覚えていてもよいかもしれません。

以上です。


参考
generator - python one-line list comprehension: if-else variants - Stack Overflow
python - break list comprehension - Stack Overflow
List Comprehensions in Python. Python Tutorials.
List Comprehensions — Python公式ドキュメント

0 件のコメント: