『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

イテレータとジェネレータ

イテレータ

Python における関数型スタイルのプログラミングの核となる機能がイテレータです。イテレータとは要素を順番に取り出すことのできるオブジェクトです。Python では for文でループ処理を実行するときに、リストやタプルを自動的にイテレータに変換しています。
 
イテレータに変換できるオブジェクトのことをイテラブル・オブジェクトとよびます。リスト、タプル、ディクショナリなどはイテラブルです。イテラブル・オブジェクトをイテレータに手動で変換するときは組み込み関数の iter() を使います。

# PYTHON_ITERATOR

# In[1]

# リストを定義
my_list = ["電子", "陽子", "中性子"]

# リストをイテレータに変換
it = iter(my_list)

# イテレータから要素を取り出す
a = next(it)

print(a)
# 電子

上のコードにあるように、イテレータから要素を取り出すときには next()関数 を使います。イテレータは前に取り出された要素のインデックスを記憶していて、next()関数を使うたびに次の要素を取り出します(イテレータは内部に __next__() という、次の要素を取り出すメソッドを備えています)。ある意味、イテレータは極めて単純な機能しかもたないオブジェクトです。イテレータにできることは次に進むことだけです。前の要素を取り出すこともできないし、自身のコピーをとることもできません。

すべての要素を取り尽くしたあとに next() で要素を取り出そうとすると、StopIteration という例外を発生し、イテレータに要素が残っていないことを知らせます。

# In[2]

# リストを定義
my_list = ["電子", "陽子", "中性子"]

# リストをイテレータに変換
it = iter(my_list)

# イテレータから要素を取り出す
next(it)
next(it)
next(it)
next(it)

'''
StopIteration
Traceback (most recent call last)
 in ()
      9 next(it)
     10 next(it)
---> 11 next(it)
StopIteration:
'''

ジェネレータ

Pythonのジェネレータとはイテレータを定義するための特殊な関数です。
 
普通の関数は呼び出されたときにローカル変数が作られてから、関数内部の命令文によって変数が操作され、return文のところで値を返すと、その変数は破棄されます。そして関数が再び呼び出されると、また新しいローカル変数が作り直されて処理を実行します。
 
これに対してジェネレータは return の代わりに yield という命令文でローカル変数を返して処理を終了します。その際にジェネレータはローカル変数を破棄せずにメモリに残しておきます。次に呼び出されたときには、その変数を使って処理を続行するのです。以下に簡単なジェネレータを定義してみます。

# PYTHON_GENERATOR

# In[1]

# ジェネレータの定義
def particle():
    for k in ["電子", "陽子", "中性子"]:
        yield k

# ジェネレータを作成
p = particle()

# ジェネレータから要素を取得
for i in range(3):
    print(next(p))

# 電子
# 陽子
# 中性子

yield を含む関数はすべてジェネレータとして扱われます。Python が yield文 を検出すると特殊な方法でコンパイルするような仕組みになっています。サンプルコードにあるように、ジェネレータ自体は呼び出されてもイテレータを返すだけです。このイテレータに対して next() が使用されたときに初めて、関数内部に定義した処理内容が実行されるという仕組みになっています。
 
ジェネレータはジェネレータ式とよばれる記法を使って、より簡単に定義することができます:

(式 for 繰り返し変数 in イテラブル)

内包表記と似ていますが、内包表記は [ ] で括るのに対して、ジェネレータ式は ( ) で括って定義します。さきほどのサンプルコードをジェネレータ式を使って書き直すと次のようになります。

# In[2]

# リストの定義
my_list = ["電子", "陽子", "中性子"]

# ジェネレータ式
it = (x for x in my_list)

print(next(it))
print(next(it))
print(next(it))

# 電子
# 陽子
# 中性子

コメント

  1. あとりえこばと より:

    【GPT解説】Pythonのイテレータとユーモア?

    [Q] イテレータについて、ユーモアを交えて説明してください。

    [ChatGPT] イテレータに関するユーモアですね。ちょっと考えてみます。ある日、イテレータが道端でジェネレータに出会いました。イテレータは、「こんにちは!ジェネレータさん!私はイテレータといいます。あなたは何をしているんですか?」と尋ねました。
    ジェネレータは、「私は新しい値を生成して、それをあなたに返すことができるんだよ」と答えました。イテレータは、「それはとても素晴らしいことですね。でも、私は一方通行のループを作ることができます。あなたはできますか?」と尋ねました。

    ジェネレータは少し考えた後、「ああ、私は一方通行だけでなく、後戻りもできるんだ。私は必要な値を生成するために、どんな場所でも自由に移動できるんだよ」と答えました。イテレータは「すごい!それはどうやってできるんですか?」と尋ねました。ジェネレータは、自慢げに笑いながら答えました。「それは、私がイテレータに比べて”ジェネレート”(生成する)ことに特化しているからさ!」

    うーん、ちょっと抽象的になってしまいましたね。でも、イテレータとジェネレータの違いを理解するのには役立つかもしれません!