今回はPythonにおけるyield文について、やさしく解説していきたいと思います。
この記事では
- yieldとは
- イテレータとジェネレータとは
- yieldの基本的な使い方
- yield fromの使い方
など、基本的な内容から、より実践的な内容に関してもわかりやすく解説していきたいと思います。yield文についてわからなくても、基礎からしっかりと解説していきます!
※この記事のコードはPython 3.7で動作確認しました。
本記事を読む前に、Pythonがどんなプログラミング言語なのかをおさらいしておきたい人は次の記事を参考にしてください。
→ Pythonとは?特徴やできること、活用例をわかりやすく簡単に解説
なお、その他のPythonの記事についてはこちらにまとめています。
yieldとは
yieldは、関数を一時的に実行停止させることが出来る機能を持つ文です。
その時点での戻り値を返し、そしてまた再開させることができます。yieldについて知るには、イテレータとジェネレータについて知らなければなりません。
イテレータとジェネレータ
Pythonにはイテレータとジェネレータという概念があります。
イテレータとは、反復して要素を取り出すことが出来る型のことを言います。Pythonのリストやセット、辞書型はイテレーションすることが出来るので、これらのオブジェクトはイテレータになります。
また、ジェネレータは、イテレータの一種になります。要素を取り出すごとに処理を実行して、要素を生成することが出来ます。
そしてこのジェネレータを、yieldを使って作ることが出来ます。
yieldを使うメリットとは
yieldを使うメリットについて解説します。
例えば、return文でそのまま値を返す関数を作ったとします。一度に大きなリストが返ってくるような関数だと、たくさんのメモリを一度に消費してしまうことになります。
そのようなときは、yieldを使う事でその莫大な量の戻り値を小分けにして返すことが出来ます。これによって関数の実行をその都度中断し、少量ずつデータを返す事でメモリの消費量を抑えることが出来るようになります。
yieldの基本的な使い方
では、yieldの基本的な使い方をご紹介していきたいと思います。
yieldは、基本的には関数の中で使われる文になります。
基本的な構文は、
def function(): yield hogehoge
になります。
こちらのコードをご覧ください。
def myfunc(): yield 'Hello'
こちらのコードはreturn文のように、その関数が返す値を指定しています。
基本的にはこのような構文で実行することが出来るのですが、これではyieldのありがたみがあまりわからないかもしれません。
そこで、forループ内でyieldを使う方法について、解説していきたいと思います。
forループ内でyieldを使う方法
yieldは、forループの中で一般的に使われます。
こちらのコードをご覧ください。
def myfunc(): yield 'one' yield 'two' yield 'three' for x in myfunc(): print(x)
実行結果
one two three
こちらのコードでは、myfuncという関数内にyield文を3つ使っています。
forループは関数内で使用したyieldを全て処理するまで作動し続けます。
forループ内のみの解説だとわかりにくいかもしれないので、Ipythonで対話的にmyfuncを呼び出し実際にどう動いているのかを見てみましょう。
こちらのIPython shellでの例ををご覧ください。
[1]: def myfunc(): ...: yield 'one' ...: yield 'two' ...: yield 'three' In [2]: x = myfunc() In [3]: x.__next__() Out[3]: 'one' In [4]: x.__next__() Out[4]: 'two' In [5]: x.__next__() Out[5]: 'three' In [6]: x.__next__() --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-6-e717e1c1f535> in <module> ----> 1 x.__next__() StopIteration:
こちらのコードではIPythonを使って、先ほどと同じmyfuncを定義しています。
forループを使った時とは違いyieldの回数分、__next__()というメソッドを呼びだすことで、先ほどと同じ結果を得ることができましたね。
こうすることで、yieldでその都度返してほしい値がきちんと返ってくることが確認出来ます。
また、yieldを含む関数の中でfor文を使うこともできます。
def myfunc2(x:int): # 0からxまでの値を2倍して返す for x_ in range(x): yield x_*2 for i in myfunc2(5): print(i) # 出力 0 2 4 6 8
ジェネレータでyieldを使う方法
forループの外でyieldを使う方法もあります。
先程解説したように、yieldを含む関数はジェネレータになります。
先ほどのIPythonを使用したサンプルコードでも少し触れたnext関数というとても便利な関数について解説します。
こちらのコードをご覧ください。
def myfunc(): yield 'one' yield 'two' yield 'three' generator = myfunc() print(next(generator)) print(next(generator)) print(next(generator))
実行結果
one two three
こちらのコードでは、yieldを含む関数を呼び出してジェネレータを格納するオブジェクトを作成しました。
ジェネレータのnext関数は、yieldまでの処理を実行しその都度、値を返します。今回は3つのyieldを使用したので3回、nextを呼びました。
実は、Python3以前のバージョンに存在していたnextメソッドが無くなったのですが、その代わりとして__next__()メソッドが導入されました。
__next__()はアンダースコア付きの特殊メソッドであるため、nextという組み込み関数が用意されました。
今回ご紹介したnext関数は、特殊メソッドの__next__()を呼び出してくれます。
yieldの数よりも多くnextを呼ぶと、このようなエラーが出力されるので注意してくださいね。
Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
yield fromについて
では、基本的なyieldを使う方法を学んだところで、yield fromについて解説したいと思います。
yield fromは主に、ジェネレータを小さく分割する為に使います。これはPython3.3から追加された構文なので、それ以前のバージョンでは使用出来ません。
yield fromの使い方
では、実際にyield fromを使ったコードをご紹介したいと思います。
こちらのコードをご覧ください。
def generator1(): yield 'one' def generator2(): yield 'two' def generator(g1, g2): yield from g1 yield from g2 gen = generator(generator1(), generator2()) for x in gen: print(x)
実行結果
one two
こちらのコードでは、まず二つのジェネレータであるgenerator1とgenerator2を作成しました。そして、二つのジェネレータをまとめるgeneratorという名前のジェネレータを作成しました。
さらに、先ほどのサンプルコードのように、generatorを呼び出し変数genに格納しました。そして、そのジェネレータをforループ内で処理させました。
実行結果を確認すると、generator1ジェネレータが実行されその後generator2ジェネレータが実行されています。
まとめ
この記事では、yieldを使う基本的な方法をご紹介しました。
特に覚えておきたいことは、
- イテレータとジェネレータについて
- 繰り返し処理はyieldを使うとメモリの消費が少ない
- forループ内での処理かジェネレータのnext関数を使う
- まとめたいときはyield fromを使う
になります。
もしyieldの使い方を忘れてしまったときは、ぜひこの記事を読み返してみてください。