意外と使ってない?! 癖になるPythonの内包表記を使ってみよう

「他のプログラミング言語になくて、Python特有の面白い言語機能は?」と聞かれて、最初に思いつくのは「リストの内包表記(英語: list comprehensions)」だろう。この内包表記というのは、手軽にリストを生成する便利な機能だ。しかし、他のプログラミング言語ではなかなかお目にかかれない機能なので、それほど使ったことがないという人も多いだろう。そこで、今回は、基本に返って、Pythonの内包表記について、フォーカスしてみよう。

基本的な使い方

まずは、内包表記の基本的な使い方を見てみよう。そもそも、たくさんの値を一つの変数で管理できるのがリストだ。そして、内容表記を使えば、そのリストを手軽に初期化できる。ここで、0が100個入ったリストを作成してみよう。以下のように記述する。

 [0 for i in range(0, 100)]

文法のテストを行うときは、PythonのIDLEか、Jupyter Notebook(本連載の2回目を参照を利用すると良いだろう。結果をすぐに確かめることができる。

  • IDLE上で内包表記を使って、0が100個入ったリストを作ったところ

    IDLE上で内包表記を使って、0が100個入ったリストを作ったところ

  • Jupyter Notebookで0が100個入ったリストを作ったところ

    Jupyter Notebookで0が100個入ったリストを作ったところ

次に、0から99まで連番の値を持つリストを作成してみよう。

 [x for x in range(0, 100)]
  • 0から99まで連番を持つリストを作成したところ

    0から99まで連番を持つリストを作成したところ

もし、内包表記を使わないで書くと、以下のようになるだろう。内包表記がいかに便利であるか分かるだろう。

 res = []
 for x in range(0, 100):
     res.append(x)
 res

つまり、内包表記を記述する際には、以下のような書式で記述する。

 # 書式
 [ 値 for カウンタ変数 in イテレータ ]

まず、for文で繰り返した分だけ、値がリストに代入される。そして、その際、カウント変数を値の部分に記述すれば連番を作成できるし、値の部分に固定値を指定すれば同じ値が繰り返し分作成されるというものだ。

出力する値を加工する

次に、先ほどの内包表記を少しアレンジして、任意の値の入ったリストを作成してみよう。例えば、[0, 1, 0, 1, 0, 1 ... ]のように、0と1が交互に出てくるリストを作ってみよう。そのためには、以下のように記述する。

 [i % 2 for i in range(0, 100)]
  • 0と1が交互に出てくるリスト

    0と1が交互に出てくるリスト

また、内包表記のよくある例だが、2の倍数を持つリスト、0,2,4,6,8...を作成するには、以下のように記述すれば良い。

 [i * 2 for i in range(0, 100)]
  • 2の倍数を持つリストを作成する例

    2の倍数を持つリストを作成する例

内包表記で値のフィルタリング

また、次のように、ifを加えた書式で記述すれば、カウンタ変数の値に応じて、値をフィルタリングできる。

 # 書式
 [値 for カウント変数 in イテレータ if フィルタ条件]

例えば、値の部分を加工せず、ifを加えることで、3の倍数だけを持った99までのリストを作ることができる。

 [i for i in range(0, 100) if i % 3 == 0]
  • ifを使って3の倍数だけを持つリストを作る例

    ifを使って3の倍数だけを持つリストを作る例

このifフィルタを実用的に使う例を一つ紹介しよう。これは、カレントディレクトリにあるファイルを列挙し、その中から、ファイルの更新日が24時間以内のものを表示するプログラムだ。

 import os, glob, time
 [f for f in glob.glob("*") if os.stat(f).st_mtime > time.time() - 60*60*24 ]
  • 24時間以内に更新したファイルを列挙する

    24時間以内に更新したファイルを列挙する

ここで使った標準モジュールの関数に関してだが、glob.glob("*")でカレントディレクトリのファイルの一覧を列挙し、time.time()で現在時刻をエポック秒で取得する。わずか一行のプログラムで、ファイルの列挙から更新日時によるフィルタリングまで指定できるので便利だ。

入れ子構造にすることも可能

そして、内包表記のすごいところは、forをネスト(入れ子構造に)して記述することができる点にある。例えば、[(0,0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]のような値が連続する二次元のリストを作ってみよう。

 [(x, y) for x in range(3) for y in range(3)]
  • forを二つ重ねて二次元のリストを作る例

    forを二つ重ねて二次元のリストを作る例

これを利用すれば、九九の表を作ることも簡単だ。

 # 九九のリストを作る
 kuku = [(x,y,x*y) for x in range(1, 10) for y in range(1,10)]
 # 表として出力
 s = ""
 for x,y,z in kuku:
     s += "{0:5d}".format(z)
     if y == 9: s += "\n"
 print(s)
  • 九九の表を出力するプログラム

    九九の表を出力するプログラム

まとめ

どうだろう。今回紹介したプログラムは、いずれも短いものだったが、forをネストさせたりifでフィルタを指定することで複雑なデータを持つリストを生成することができた。内包表記を使えば、短い表記でリストを生成できて便利だ。

しかし、内包表記になれてしまうと、何でも、内包表記で記述したくなってしまうのが、この表記方法の欠点だ。と言うのも、あまり複雑な内包表記を書くと、プログラムのメンテナンス性が悪くなってしまうのだ。使いすぎに注意しよう。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2005年IPAスーパークリエイター認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。