2014/09/16

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

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


内包表記とは?

内包表記とは、リストや辞書におけるループ処理をかんたん・シンプルに書くための記法です。

たとえば、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 )。内包、外延いずれとも数学における集合を表すときの表現方法のようです。


種類

内包表記の種類として次の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>


セット内包表記

セット内包は {} で定義します。
{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}
要素を定義する部分が「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 件のコメント: