目次

はじめに

C++/Java/C#などいくつかのプログラム言語は、基本的には似たような構文が使用できるので、どれか1つのプログラム言語に精通すれば、他のプログラム言語に置き換えることができます。
しかし、ある言語独自の書き方は簡単には置き換えができず、その点が、新たなプログラム言語を始めたときには、独自の書き方にカルチャーショックを受け、素直にその独自の書き方を受け入れられないことがあります。
その結果、せっかくの良い書き方がうまく使えなかったり読み辛かったりすることがあります。
Pythonには内包表記という、単純でコード量を削減できる書き方があるのですが、筆者はこれを使えるようになるのに少し抵抗がありました。
今回は、Pythonの内包表記について、深い理解をすべく、記事を書くことにしました。


内包表記とは

内包表記とは、本来for文(ブロック)などで繰り返し処理を行う場合に、比較的単純な処理を簡潔に記述する文法です。
for文では、繰り返し条件と処理を書くのに数行のコードを書く必要がありますが、内包表記を使うと1行で、かつ、シンプルに書くことができます。
内包表記には、list内包表記、dict内包表記、set内包表記の3種類があります。
いずれも、コレクションデータを順番に単純な処理していくような場合に使用します。
処理した結果がリストになるかディクショナリになるか集合になるかで呼び名が変わると思ってよいでしょう。

書き方の基本は、
  式 for 繰り返し変数 in シーケンス
のような書き方です。
例えば、dataxというリスト内のデータの分散を求める場合、次のようなコードになります。

内包表記を使わない場合

>>> datax = [11, 12, 13, 14, 15]
>>> total = sum(datax)
>>> num = len(datax)
>>> avg = total/num
>>> for height in datax:
...     varx = varx + (height-avg)**2
...
>>> varx = varx / num
>>> varx
2.0

内包表記を使った場合

>>> datax = [11, 12, 13, 14, 15]
>>> total = sum(datax)
>>> num = len(datax)
>>> avg = total/num
>>> varx = sum([(height-avg)**2 for height in datax]) / num
>>> varx
2.0

上記のように少し(4行→1行)ですが、コードの量が削減できます。
for文のブロックがなくなるので、内包表記に慣れると、コードがシンプルになります。
また、for文を使った場合に比べて実行速度も速くなることが多いので、積極的に内包表記を使っていくことで性能的にも良くなります。

list内包表記

list内包表記は、リストなどのシーケンスの要素を操作して新しいシーケンス(リスト)を生成する方法です。
書き方は、以下のように処理結果がリストになるように角括弧「[]」の中にforやinやifなどのキーワードを組み合わせて記述します。

リスト内包表記の書き方

[ 式 for 繰り返し変数 in シーケンス (if 条件式) ]

先ほどの分散を求める例は、この書き方になっています。
if文は、変数の条件が真の場合にのみ式が実行されます。
内包表記をするうえでif文は重要であると筆者は考えています。なぜなら、リスト内のすべての値を処理するのよりも、「ある条件に合った値を処理したい」というシーンが処理を書く中で出会うことが多いからです。
例えば、以下の例のようにリスト内の偶数だけを抽出するには、以下のようにします。
 

>>> datax = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> ev = [int(s) for s in datax if s % 2 == 0]
>>> ev
[2, 4, 6, 8, 10]

上記のようなif文使った内包表記に慣れると内包表記がより身近に簡単に感じられるようになります。(筆者もこうやって慣れてきました)

dict内包表記

dict(ディクショナリ)内包表記は、リスト内包表記と同じような書き方ですが、ディクショナリのデータを扱う表記方法です。
書き方は、以下のように処理結果がディクショナリになるように波括弧「{}」の中にforやinやifなどのキーワードを組み合わせて記述します。
なお、dict(ディクショナリ)は、キーと値がペアとなっているものの集まりです。

ディクショナリ内包表記の書き方

{ キー:値 for 繰り返し変数 in シーケンス (if 条件式) }

上記の「キー:値」の部分が、ディクショナリの要素を示しています。それ以外はリスト内包表記と同じです。
つまり、ディクショナリ内包表記とは、結果をディクショナリにする内包表記です。
例えば、数字の入ったリストをキーと値のディクショナリにする場合は、以下のようにします。

>>> nolist = [ 10, 8, 9, 7, 6, 2, 1, 3, 4, 5 ]
>>> rdict = { str(i):i for i in nolist }
>>> rdict
{'10': 10, '8': 8, '9': 9, '7': 7, '6': 6, '2': 2, '1': 1, '3': 3, '4': 4, '5': 5}

また、2つのリストを1つのディクショナリにするような場合は、組み込み関数zipを使って以下のようにします。

>>> list1 = [ 'GMT', 'BST', 'EET', 'JST' ]
>>> list2 = [ 0, 1, 2, 9 ]
>>> tzone = { i:j for i,j in zip(list1, list2)}
>>> tzone
{'GMT': 0, 'BST': 1, 'EET': 2, 'JST': 9}

set内包表記

set(集合)内包表記、これもリスト内包表記と同じような書き方ですが、扱うデータがディクショナリのようなキーと値ではなくリストのような単体の値で、かつ、結果を示す括弧が、ディクショナリと同じ波括弧「{}」となります。
単に波括弧だけだとディクショナリになりますが、中身が単体の値であれば集合となります。

集合内包表記の書き方

{ 式  for 繰り返し変数 in シーケンス (if 条件式) }

上記で示したリスト内包表記とディクショナリ内包表記の合わせ技のような感じですね。
この辺がややこしくて慣れないとわかりにくいと思います。(筆者も始めはよくわかりませんでした)

>>> heikin = { 'クラスA':60, 'クラスB':87, 'クラスC':90, 'クラスD':76 }
>>> good = { name for name in heikin if heikin[name] > 85 }
>>> good
{'クラスC', 'クラスB'}

この例は、クラス名:平均値のディクショナリから平均値を比較して、点数の良いクラス名の集合goodを作るものです。

まとめ

今回は、筆者が個人的にわかりにくいと感じたPythonの内包表記について、少し具体例も示して紹介しました。
ともあれ、内包表記は、最初は使いこなすのは難しいかもしれません。
しかし、慣れれば「あ、これは内包表記が使えるかも」と思えるようになることでしょう。
こうなるためには、やはり自分で処理ロジックを考えて手を動かしてプログラムをたくさん書くことが大切だと、筆者は感じました。

次回も、筆者がPythonのわかりにくいと感じたことを自分の理解を確認しながら1つずつ紹介していこうと思います。