Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Pythonのジェネレータ式とリスト・辞書の内包表記との違いについての解説

【Python入門】ジェネレーター式と、リスト・辞書の内包表記との違いをわかりやすく解説

Python文法解説:Pythonのジェネレータ式とリスト・辞書の内包表記との違いについての解説

Pythonのジェネレータについては前回の記事「【Python入門】ジェネレータとは?for文との関係や、yieldとprint/returnとの違いは?」において具体例を出しながら解説しました。

まずは今回の記事の前準備としてその記事を読んでおいてください。

その記事ではdef宣言によってジェネレータとなる関数を定義するコードを使いましたが、実はジェネレータを作るにはもう1つの方法があり、それが「ジェネレータ式」と呼ばれる方法です。

ジェネレータ式と辞書・リスト内包表記との違いとは?

ジェネレータ式について

まずジェネレータ式の作り方は、Python公式ドキュメントでは、

generator_expression ::=  "(" expression comp_for ")"

このように書かれています。これの意味は大雑把にいえば、

ジェネレータ式 =(~for文をつかった式~)

とすれば作れるよ、ということです。

そして、

ジェネレータ式は新たなジェネレータオブジェクトを与えます。この構文は内包表記とほぼ同じですが、角括弧や波括弧ではなく、丸括弧で囲まれます。

とも書かれています。

以上のことを踏まえて以下のようなコードを作りました。

# ジェネレータ式でジェネレータイテレータオブジェクトを作成
gnrtr_expression=(i for i in range(0, 3))

# 型を確認
type(gnrtr_expression)

これを実行すると、

<class 'generator'>

という型が表示されます。これはイテレータ型の1つであるジェネレータ型であるということです。

一見すると、上のジェネレータ式はリストや辞書の内包表記とほぼ同じに見えます。違うのは使われるカッコ(括弧)の種類の違いだけという感じです。

しかし、実際に作成されるオブジェクトはリストや辞書でもないジェネレータ型の特別なオブジェクトです。このようなジェネレータやジェネレータ式で生成されるオブジェクトはジェネレータイテレータと呼ばれます。

では、このジェネレータイテレータを使ってリストを作ってみましょう。次のコードを見てください。

# 作ったジェネレータイテレータを使ってリストを作成
i=0
list_by_gnrtr=[]
while i<len(range(0, 3)):
    element=gnrtr_expression.__next__()
    list_by_gnrtr.append(element)
    i=i+1

print(list_by_gnrtr)

ジェネレータイテレータはイテレータであり、__next__メソッドを有しています。それを使って処理を進めるということをwhile文を使って繰り返しています。

イテレータとは、過去記事「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」でも書きましたが、

さて、以上のようにイテレータとは、

・__iter__()メソッド
・__next__()メソッド

の2つを同時に持っているオブジェクトというものです

過去記事「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」

というものです。

(イテレータとイテラブルオブジェクトとの違いについては、過去記事「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」を読んでください)

さて、この実行結果は、

[0, 1, 2]

このように、きちんとリストが作れました。

最大のポイントは、コードの1行目でジェネレータ式を書いて実行した時点では、ジェネレータイテレータという特別なオブジェクトが作られるだけで、新しいリストや辞書がその時点で出来上がるわけではないということです。

すなわち、ジェネレータやジェネレータ式で作られるジェネレータイテレータは、あくまで「指示を受ければ、次の処理を1つ進める」という「処理の手順・流れ」を司るというオブジェクトであって、リストの中身や辞書を直接的に作成するものではないということが重要です。あくまでイメージですが。

では次に、このジェネレータ式との比較として、通常のリスト・辞書の内包表記を見てみましょう。

辞書・リストの内包表記について

こちらもコードの具体例を書きます。これを実行してみてください。

# リストの内包表記によるリスト作成
list_comprehension=[i for i in range(0, 3)]

# 辞書の内包表記による辞書の作成
dict_comprehension={
    x:y for x,y in zip(range(0, 3), 
                       ['apple', 'orange', 'kiwi'],
                       )
}

# 出来上がったオブジェクトを確認
print(list_comprehension)
print(dict_comprehension)

# 型確認
type(list_comprehension)
type(dict_comprehension)

それぞれ出来上がったオブジェクトの型ですが、もちろんそれぞれリスト型と辞書型となります。

なお以下のコードは、make_listという関数を定義してリストを作るものですが、これを実行すると上のリスト内包表記によるコードと同じような結果となります。実行して確かめてみてください。

# 上のリスト内包表記によるリスト作成と同じようにリストを作る関数
def make_list():
    list_by_fnc=[]
    for i in range(0, 3):
        list_by_fnc.append(i)
    return list_by_fnc

print(make_list())

このコード中の、

    for i in range(0, 3):
        list_by_fnc.append(i)

という部分が、リスト内包表記で使った、

list_comprehension=[i for i in range(0, 3)]

の部分と本質的に対応していることに注目してください。

まとめ

以上の解説を下に表として簡単にまとめてみました。

ジェネレータ式 リスト・辞書の内包表記
見た目 (〜) リスト:[〜]
辞書:{〜}
出来上がるオブジェクト(その型) ジェネレータイテレータ(イテレータ型としてのジェネレータ型) リスト(シーケンス型としてのリスト型)
辞書(マッピング型としてのdict型)

となります。このようにジェネレータ式と内包表記は見た目上よく似ていますが、全く異なる機能を持ちます。これらのことがわかっていても、やはり見間違えることは多いと思うので注意したいですね。

そこで見間違いを防ぐために、コメントを積極的に書いたり、変数名を工夫したり、アノテーションを利用したりなどを考えたいですね。

愛を分かち合いましょう