内包表記 (Comprehension)

内包表記 (Comprehension)

内包表記 (Comprehension)

 Python には リストディクショナリ などのイテラブルオブジェクトの各要素を操作して、新しいイテラブルオブジェクトを作り出す 内包表記 (Comprehension) とよばれる記法が備えられています。

リスト内包表記

 list オブジェクトの操作は案外面倒なものです。たとえば、リストのすべての要素に 1 を加えた新しいリストを作ろうとすれば、for文を使って次のようなコードを書く必要があります。

# リスト型データを定義
num_list = [1, 2, 3, 4, 5]

# 空白のリストを用意します
new_list = []

# リストの各要素に 1 を加えます
for x in num_list:
    new_list.append(x + 1)

print(new_list)
[2, 3, 4, 5, 6]

 上のサンプルにおける for 文のコードブロックでは、リストの各要素に対する操作が行われています。
 このような処理は リスト内包表記 を使って次のように書き換えることができます。

# リスト内包表記のサンプルコード

# リスト型データを定義
num_list = [1, 2, 3, 4, 5]

# リストの各要素に 1 を加えます
new_list = [x + 1 for x in num_list]

print(new_list)
[2, 3, 4, 5, 6]

 リストの各要素に 1 を加えた新しいリストを作る、という処理を

new_list = [x + 1 for x in num_list]

という1行のコードにまとめています。リスト内包表記の基本形は

 [x の処理 for x in イテラブルオブジェクト]

です。x は繰り返し変数なので、i や k など好きな文字を使ってください。もとのイテラブルオブジェクトは必ずしもリスト型である必要はありませんが、戻り値はリストになります。もとのイテラブルオブジェクトから要素を順に取り出して x に入れて、指定した処理を施してから、新しいリストの要素としてゆきます。
 

内包表記を用いた平方和の計算

 リストの各要素を 2 乗してすべて足し合わせてみましょう。
 すなわち平方和の計算です。
 これも内包表記を用いると簡潔に表現できます。

# 内包表記を用いた平方和の計算

# リスト型データを定義
num_list = [1, 2, 3, 4, 5]

# 内包表記で平方和を計算
s = sum([x ** 2 for x in num_list])

print(s)
55

 上のコードでは、元のリストの要素をすべて 2 乗した要素をもつ新しいリストを作ってから、sum 関数でそれらの要素をすべて足し合わせることによって、平方和の計算を行なっています。
 

条件式を添えます

 今度は内包表記の基本形の後ろに条件式を添えてみます。

 [x の処理 for x in イテラブルオブジェクト if x の条件式]

 この形式で記述した場合、if のあとに添えられた条件式を満たしている場合に限って、x に処理が施されて新しいリストに追加されます。言い換えると、もとのリストから取り出された要素が条件を満たしていない場合、その要素は破棄されることになります。

 簡単な例として、もとのリストから 100 を超える要素だけを取り出すコードを書いてみます。

# 条件式を含むリスト内包表記[1]

# リスト型データを定義
num_list = [78, 155, 103, 21, 59, 117]

# 100 を超える要素だけを取り出します
new_list = [x for x in num_list if x > 100]

print(new_list)
[155, 103, 117]

 次はもう少し複雑な条件式です。リストの要素から "p" というアルファベットを含む単語だけを抜き出します。

# 条件式を含むリスト内包表記[2]

# フルーツのリスト
fruit_list = ["apple", "orange", "strawberry", "pear", "plum"]

# "p" を含む要素だけを取り出します
new_list = [x for x in fruit_list if x.find("p") != -1]

print(new_list)
['apple', 'pear', 'plum']

 条件式のところで find メソッドを用いています。
 find メソッドは対象とする文字列の中に指定文字がみつからなければ「-1」を返してきます。「!=」は「等しくない」という意味の比較演算子です。つまり「 x が -1 に等しくないならば、それは "p" という文字を含むのだから要素に加えなさい」ということです。
 

ディクショナリ内包表記

 キーと値のペアを要素にもつディクショナリの内包表記は、リスト内包表記よりも少し複雑です。基本的な書き方は次のようになります。

 {x の処理 : y の処理 for x, y in ディクショナリ.items()}

 items メソッドを使うことによって、キーと値の両方に対して処理を行なって新しい辞書を作ります。x, y はそれぞれキーと値が入る変数です。
 

値を処理します

 書籍の表題をキー、税抜価格を値とするディクショナリを定義して、書籍の表題と税込価格のディクショナリを作ります。この記事を執筆している 2018 年現在の消費税は 8% (2019 年に 10 % となる予定) なので、税抜価格を 1.08 倍にして小数点を捨てれば税込価格が得られます。

# ディクショナリ内包表記のサンプルコード[1]

# 書籍と税抜き価格のディクショナリ
# _ex は tax excluded(税抜)を意味する添字
book_ex = {"Pythonスタートブック":2500,\
           "Pythonの絵本":1780,\
           "入門Python3":3700}

# 書籍と税込み価格のディクショナリ
# _in は tax included(税込み)を意味する添字
book_in = {x:int(y*1.08) for x, y in book_ex.items()}

print(book_in)
{'Pythonスタートブック': 2700, 'Pythonの絵本': 1922, '入門Python3': 3996}

 処理するのは値のほうなので、x には何も手を加えておらず、もとの辞書のキー(税抜価格)のみが書き換えられて新しい辞書が作られています。
 

キーと値を入れ替えます

 ディクショナリ内包表記を使うと、簡単にキーと値を入れ替えることができます。以下のサンプルコードでは「書籍:発行元(出版社)」の辞書から「発行元(出版社):書籍」の辞書を作ります。

# ディクショナリ内包表記のサンプルコード[2]

# 書籍と発行元(出版社)のディクショナリを定義
# _pub は publisher の略
book_pub = {"詳細! Python3入門ノート":"ソーテック社",\
            "かんたんPython":"技術評論社",\
            "スラスラわかるPython":"翔泳社"}

# キーと値を入れ替えます
pub_book = {p:b for b,p in book_pub.items()}

pub_book
{'ソーテック社': '詳細! Python3入門ノート', '技術評論社': 'かんたんPython', '翔泳社': 'スラスラわかるPython'}

 内包表記のコード

pub_book = {y:x for x,y in book_pub.items()}

において、x は書籍、y は発行元に対応する変数ですが、for の前の処理文が「 y:x 」と逆順になっています。つまり、もとの辞書の値をキー、キーを値にするという処理が行われて、キーと値の交換が行われているのです。
 

set 内包表記

 set とリストは非常によく似た性質をもつオブジェクトなので、set 内包表記はリスト内包表記とほぼ同じような記述で使えます。

 {x の処理 for x in イテラブルオブジェクト if x の条件式}

 とはいえ、せっかくですから、set ならではの「要素の重複を取り除く」という性質を生かしたサンプルコードを書いてみます。最初文字列を定義して、その中で使われているアルファベットを重複なしに小文字で列挙してみます。

# set内包表記のサンプルコード

# 文字列を定義
my_string = "Learn Python in One Day and Learn It Well"

# 文字列を1文字ずつ分割して重複は省く
# ただし、空白は除く
my_words = {x.lower() for x in my_string if x != ' '}

my_words
{'a', 'd', 'e', 'h', 'i', 'l', 'n', 'o', 'p', 'r', 't', 'w', 'y'}

 文字列は1文字ずつにインデックスが割り当てられたオブジェクトなので、それを内包表記で1文字ずつ取り出す操作を行なうと、文字列はバラバラにされて、1文字を1要素として set の中に格納されていきます。
  

内包表記は速度でも有利です

 内包表記を応用すると、複数の変数を組み込んで、2 次元や 3 次元の配列をたった1行のコードで生成したりすることもできるようになります。

 しかし、内包表記はコードをすっきりとまとめるためだけに存在するのではなく、処理速度を大幅に向上させるという利点をもちます。for 文などを使ってイテラブルオブジェクトの要素を操作する場合には、どうしてもコードブロックで append 関数などを呼び出さなくてはなりません。一方で内包表記はリストの要素を直接操作する記法なので、大きなデータを扱う場合には速度面で圧倒的に有利です。

 内包表記は何か特別で高級なことをやろうという記法ではなく、むしろその逆で、関数などを使わずに操作をできるだけ単純化させようと考えて設計されているのです。