今回はジェネレーター式とリストなどの内包表記を取り上げる。これらはラムダ式と並んでPython的なコードを書くのによく使われる要素だ。
前回はPythonの関数、無名関数、ジェネレーターを紹介した。今回はその続きというわけではないが、ジェネレーターを生成するもう1つの方法であるジェネレーター式と、それに似た構文でリストや辞書を操作できる内包表記を取り上げる。今回もこれまで同様に、Python Tools for Visual Studioが提供する[Interactive]ウィンドウを利用して、動作を確認している(Python 3.5.2)。
前回のおさらいになるが、まずはジェネレーターオブジェクトを返すジェネレーター関数の定義を見ておく。
>>> def fibgen():
... n0, n1 = 0, 1
... yield n0
... yield n1
... while True:
... n0, n1 = n1, n0 + n1
... v = yield n1
... if v == "terminate":
... break
...
>>> fg = fibgen()
>>> for num in range(10):
... print(next(fg))
…… 省略 ……
このような複雑な計算を行うジェネレーターはdefキーワードを使用しないと定義できないが、シンプルなジェネレーターは「ジェネレーター式」を使っても作成できる。ジェネレーター式のシンプルな構文を以下に示す。
( 式 for ターゲットリスト in 反復可能オブジェクト )
「式」にはジェネレーターオブジェクトが返す値を、「ターゲットリスト」には「反復可能オブジェクト」(イテラブル)が返送する値を受け取るターゲット(「リスト」という表現から分かるように複数のターゲットを記述可能。受け取った値を「式」中で使用するための変数リストと考えればよい)を記述し、それらをかっこで囲む。
文章だけでは分かりにくいので、実際にシンプルな例を見てみよう。
>>> generator = (x * 2 for x in range(5)) # ジェネレーター式によるジェネレーターオブジェクトの作成
>>> for num in generator:
... print(num)
...
0
2
4
6
8
強調表示部分がジェネレーター式でジェネレーターオブジェクトを作成しているところだ。このジェネレーターオブジェクトは「range(5)が返す値をxに受け取り、それを2倍したものを返送」する。結果もその通りになっていることが分かる。この構文は、以下で取り上げるリストの内包表記などと同様であることから、「ジェネレーター内包表記」と呼ぶこともある。
なお、このジェネレーター式と同様な処理を行うジェネレーターを返すジェネレーター関数は次のようになる。
# 比較用:generator = (x * 2 for x in range(5))
>>> def dblgen():
... for x in range(5):
... yield x * 2
前回紹介したlambdaキーワードを利用したラムダ式と同様にこの構文はあまり他の言語では見かけないPython独自のものであり、慣れないうちは分かりにくいが、使い込んでいけば便利に使えるだろう。
次に、同様な構文構造を持つリスト、集合、辞書の内包表記について見ていこう。
内包表記とは既に存在している何らかのデータ(シーケンスや反復可能オブジェクト)を基にして、新たなデータを作成するための簡便な記述法だと考えられる。ジェネレーター式と大きく異なるのは、「式 for ターゲットリスト in 反復可能オブジェクト」を囲むものだ。リストでは「[]」で、集合と辞書では「{}」で囲む。辞書ではさらにキー/値を表現するために「式」の部分にコロン(:)で区切った式を2つ記述することになる。後述するがタプルには内包表記はない。
# リストの内包表記
[ 式 for ターゲットリスト in 反復可能オブジェクト ]
# 集合の内包表記
{ 式 for ターゲットリスト in 反復可能オブジェクト }
# 辞書の内包表記
{ 式1 : 式2 for ターゲットリスト in 反復可能オブジェクト }
内包表記は、式(あるいは「式1 : 式2」)を評価した結果を要素とする新たなリスト/集合/辞書を作成する。これに対して、ジェネレーター式が返すのはジェネレーターオブジェクトである点は大きな違いだ(内包表記では実際にリストなどが作成されメモリがその要素に必要なだけ占有されるが、ジェネレーター式では全要素に必要なメモリが占有されることはない)。
以下では基本要素を見ていく。まずはリストの内包表記から。
>>> l1 = list(range(10))
>>> l1
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l2 = [x * 2 for x in l1] # リストの内包表記
>>> l2
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> l3 = [x * 2 for x in range(10)] # もちろん、こう書いてもよい
「{}」で囲むことを除けば、集合も同様だ。
>>> s1 = set(range(0, 10, 2))
>>> s1
{0, 8, 2, 4, 6}
>>> s2 = {x * 2 for x in s1} # 辞書の内包表記
>>> s2
{0, 16, 4, 12, 8}
>>> s3 = {x * 2 for x in range(0, 10, 2)}
>>> s4 = {x * 2 for x in l1} # リストを基に集合を作成
辞書については先にも述べたように、キーと値が必要になるので、少し書き方が変わる。ここでは「式」の部分が「n : chr(n + 65)」となっている。chr関数は引数に整数を取り、それをUnicodeのコードポイントとして、対応する1文字の文字列を返すので、変数dの内容を見ると分かるようにA〜Eの各文字が得られている。
>>> d = { n : chr(n + 65) for n in range(5) } # 辞書の内包表記
>>> d
{0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E'}
なお、タプルには内包表記がない。上で見た通りジェネレーター式で「()」を使用しているので、「(x for x in ...)」と書いてもこれはタプルの内包表記ではなく、ジェネレーター式となる。タプルが欲しい場合には、単純に組み込み関数tupleの引数に内包表記形式の式を書けばよい。このとき、内包表記を囲むかっこは省略できる。なお、リストや集合、辞書も同様に組み込み関数list/set/dictに内包表記を表記できるが、関数dictに関してはかっこを省略できないので注意しよう。以下に例を示す。
>>> tuple(x * x for x in range(5)) # 内包表記を関数tupleの引数として記述
(0, 1, 4, 9, 16)
>>> list(x * x for x in range(5)) # リストの場合
[0, 1, 4, 9, 16]
>>> set(x * x for x in range(5)) # 集合の場合
{0, 1, 4, 9, 16}
>>> dict(x : x * x for x in range(5)) # 辞書では{}を省略できない
File "<stdin>", line 1
dict(x : x * x for x in range(5))
^
SyntaxError: invalid syntax
>>> dict({x : x * x for x in range(5)})
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
次ページでは少し高度な使い方の例を見る。
Copyright© Digital Advantage Corp. All Rights Reserved.