スポンサーリンク

コードが書ける!数式が書ける!AAが書ける!スタンプが貼れる!

無料の匿名掲示板型SNS「このはちゃんねる

新規会員募集中!

【Python】yield文でジェネレーターを作る【入門第53回】

170, 2019-09-12

目次

ジェネレーターとは?

こんにちは、narupoです。

今回はちょっと変わったものをご紹介します。
その名もジェネレーターというものです。
このジェネレーターは、大量のループを省エネで実行したい時に便利です

ジェネレーターについて、具体的には↓を見ていきます。

  • 大量のループを実行すると困ること
  • ジェネレーターの作り方
  • yield文の動作

大量のループを実行すると困ること

大量のループで大量の要素を持ったリストを作りたいとします。
その場合、考えられるのは↓のようなコードです。

lst = []
for i in range(100):
    lst.append(i)

↑のようなコードでリストを作った後に、↓のようにしてリストの要素の値を取り出すとします。

print(lst.pop(0))
print(lst.pop(0))

これはこれで実直なコードと言えます。
しかし、ループ回数が1000000000001000億)ぐらいになったらどうでしょうか。
たとえば↓のようにです。

# 実行しないでください
lst = []
for i in range(100000000000):
    lst.append(i)

↑のようにループ回数が多いと、CPUがビジーになってフリーズしたりします。
その間、プログラムを使っているユーザーは困ったことになります。
処理に時間がかかってはユーザーの快適度が落ちるからです。

こういった場合、ループでリストを作らなくても、ジェネレーターを使えば非常に省エネで同様の処理を実現することができます。

ジェネレーターの作り方

ジェネレーターを作るにはまずジェネレーター関数を書きます。
ジェネレーター関数はまず普通に関数を書いて、

def gen_func():
    pass

その中にyield文を書きます。

def gen_func():
    yield 1

↑のyield文は整数1を返すyield文です。
↑のように関数の中にyield文を書くと、その関数はジェネレーター関数になります。

ジェネレーター関数を呼び出すと、ジェネレーター・オブジェクト(ジェネレーター)を作ることができます。

gen_obj = gen_func()

このジェネレーターをnext関数に渡すと、yieldが返す値を取り出すことができます。

print(next(gen_obj))

↑のコードの実行結果は1になります。

yield文の動作

さきほどのジェネレーターの動作はいったいなにが便利なのかわからないものでした。
ここからジェネレーターの本領を見ていきたいと思います。

まず↓のようなジェネレーター関数を書きます。

def gen_func():
    i = 0
    while True:
        yield i
        i += 1

変数iを無限ループでインクリメントして、yield文で変数iを返すジェネレーター関数です。
このジェネレーター関数から作ったジェネレーターを↓のようにnextで呼び出すと、

gen_obj = gen_func()
print(next(gen_obj))
print(next(gen_obj))
print(next(gen_obj))

↓のような結果になります。

0
1
2

じつは、yield文はreturn文とは全く違う文です。
yield文を呼び出すと、yield文は値を返してそこで処理をストップします。
もう一度さきほどのジェネレーター関数を見てみましょう。

def gen_func():
    i = 0
    while True:
        yield i
        i += 1

ジェネレーターがnextに渡されると、ジェネレーターは内部の状態をyield文まで進めます。
そしてyield文が実行されると、ジェネレーターは内部の状態をそこでストップします。
つまり1回目のnext(gen_obj)の呼び出しで、ジェネレーターの内部の状態がyield iまで進むわけです。
そして2回目のnext(gen_obj)が実行されると、ジェネレーターの内部の状態がyield iから進んでi += 1と来て、while True:に戻り、再びyield iまで来ます。
3回目のnext(gen_obj)は2回目の動作と同じです。

このようにジェネレーターは無限に整数を作ることができます。
しかも内部の状態をちょっとずつ進めながら整数を作っていきます。
無限ループで一気に整数を作る方法に比べると、かなり省エネな動作なのがわかります。

yield文が終了した場合

ジェネレーターの内部の状態が最後まで進んだときにnextを実行すると、ジェネレーターはStopIterationを送出します。
たとえば↓のようなジェネレーター関数で作ったジェネレーターを、↓のようにして呼び出すと、

def gen_func():
    yield 1

gen_obj = gen_func()
next(gen_obj)
next(gen_obj)

↓のような結果になります。

Traceback (most recent call last):
  File ".\sample.py", line 6, in <module>
    next(gen_obj)
StopIteration

さきほどのジェネレーター関数を見てみましょう。

def gen_func():
    yield 1

最初のnextの呼び出しでジェネレーターの状態がyield iまで進みます。
その次のnextyield iから進んで、関数の終わりにきます。
関数の終わりに来ると、ジェネレーターはStopIterationを送出します。
「次」がないからですね。

おわりに

ジェネレーターは非同期処理でも使われます。
覚えておくといざという時にいいことがあるかもしれません。
以上、次回に続きます。

また見てね

投稿者名です。64字以内で入力してください。

必要な場合はEメールアドレスを入力してください(全体に公開されます)。

投稿する内容です。

スポンサーリンク

スポンサーリンク

スポンサーリンク

スポンサーリンク