プログラミング初心者の勉強ブログ #99
Pythonオンライン学習教材として評判のPyQ(https://pyq.jp/)によるPython学習記録です。Rubyを多少かじり、第二外国語としてPythonを少しづつ学んでいきます。(内容はRubyとの比較が多くなるかもしれません。)
今回はスクレイピング学習の途中で出てきたyield文について、イテレータとジェネレータの違いから理解していきたいと思います。
目次
[toc]
yieldとは
yieldを関数内で使うと、yieldを書き込んだ地点の戻り値を返してくれます。通常関数の戻り値は「return」の後に続けます。returnは戻り値を返すと、その後ろにまだ続きがあっても関数を抜けてしまします。一方で、yieldは戻り値を返した後、その続きから処理を再開することができます。
// returnのとき def generator1(): return 'hoge' print(generator1()) #--> hoge // yieldのとき def generator2(): yield 'hoge' yield 'hogehoge' yield 'hogehogehoge' for i in generator2(): print(i) #--> hoge #--> hogehoge #--> hogehogehoge
イテレータとジェネレータ
イテレータとは、反復して要素を取り出すことができる型を指します。Pythonのリストやタプル、セット、ディクトは、for文で繰り返し要素を取り出すことができ、イテラブルな型としてこれに該当します。ジェネレーターは関数を一時停止させ、途中経過の結果を返すことができる機能であり、イテレータの一種でもあります。
つまり、イテレータは「反復的に要素を取り出すことができる型」のことを指し、ジェネレータはイテレータの中でも「要素を生成できる」ものを指すということです。
yield文が含まれた関数はジェネレータとなり、含まれているyieldの回数分要素を生成してくれます。returnでは生成タイミングは1回であるのに対し、yieldは複数回生成タイミングを持たせることができるという訳です。
※yieldとreturnは同じ関数内で併用できない。
// returnのとき def generator1(): return 'hoge' # 要素の生成は関数の処理が終了したとき1回のみ print(generator1()) # --> hoge // yieldのとき def generator2(): yield 'hoge' yield 'hogehoge' yield 'hogehogehoge' # 要素の生成は3回(for文によって3回generator2関数から要素が生成されている) # 1回のタイミングで「hoge」「hogehoge」「hogehogehoge」が全て生成されている訳ではない for i in generator2(): print(i) # --> hoge # --> hogehoge # --> hogehogehoge
ジェネレータの特徴
Pythonにおいてジェネレータは、基本的にはyieldを含んだ関数のことを指します。yieldを含んだ回数分要素を生成できる訳ですが、ここにジェネレータの特徴が存在します。ジェネレーターは関数の中で処理を一時停止させ要素を生成するとともに、一時停止させた地点から処理を再び再開します。つまり、関数の中で状態を保持できるということです。この特徴がジェネレータを使うメリットに繋がっていきます。
ジェネレータのメリット
前述した特徴より、ジェネレータは通常1回で返す戻り値を複数回に分けて返すことができます。これにより、一回に発生する「メモリーの使用量の軽減」が行えます。一般的に、戻り値が膨大であればあるほどメモリーの使用量は増大します。データ量の少ない戻り値の関数であれば特に必要はありませんが、巨大なデータの計算結果を返す必要がある関数の場合はyieldを用いて計算結果が出るたびすぐ返すようにすることでメモリーの節約ができ、メモリが足りなくなってプログラムが停止してしまうような最悪の事態を回避できるそうです。
yield文と合わせて使うnext関数
nextを使うことで、yieldが書かれたところまでの処理を実行し戻り値を返してくれます。
// 引数の数値を2倍にして返す関数 def generator(number): for n in range(number): yield n * 2 // ジェネレータオブジェクトを生成し、gに格納 g = generator(5) // next関数で一つ目の要素(v1)を生成 v1 = next(g) print(v1) # --> 0 v2 = next(g) print(v2) # --> 2 v3 = next(g) print(v3) # --> 4 v4 = next(g) print(v4) # --> 6 v5 = next(g) print(v5) # --> 8 // 返せる要素がなくなるとエラーが発生 next(gen) # => StopIteration
まとめ
最近夜寝る前にドラゴンクエストビルダーズ2のゲーム実況を見るのが日課になっているのですが、どうしたらこんなすごいゲームが作れるんだろうと、プログラミングを勉強している身として技術面に対する小学生並みの感想が出てきます。ドラゴンクエストビルダーズというゲームは「ドラクエとマインクラフトの掛け合わせ」みたいなゲームで、ビルダーと呼ばれる職業の主人公がものづくりを通じて世界を豊かにするといった内容でストーリーが展開していきます。主人公は村人からの頼み事を聞き、物を作り、達成すると得られるポイントを稼ぐことでその村のレベルが上がります。ゲームの中では△ボタンを押しキャベツのタネを畑に植えると課題が達成され、村人全員から拍手喝采の賞賛が得られます。ゲームとりわけRPGというのは基本的にこういうもので(もちろん難しいものもあるとは思うが)、主人公は才に溢れヒーローであり、その主人公目線でプレイすることで充実感や達成感が得られます。ユーザーはゲームをプレイし比較的簡単な課題をクリアしてこのような満足感を得ていく訳ですが、その裏でプログラマーは△ボタンを押してタネを植えるよりもはるかに難しいアルゴリズムをきっと考えている訳であって、そんな人たちもyield文やらジェネレータがあーだこーだの勉強をしてた時期もあったのかなと、そんなことを考える最近です。
以上ありがとうございました。