Pythonやりだすと、いろいろ便利なのでオモシレーってなって、もっと知りたいってなったころに出てくる子達がいます。
iterable, iterator, generator, yield
他の言語やってる人だと大体「あー」って分かるものなのですが
とは言え、最後の yield などは初見「なんて読むんだよコレ」ってなります。
(ちなみに、読み方はカタカナだと「イールド」と言うらしいです。まあ、読めなくても特に問題ないです。)
このあたりの用語や概念は、pythonやる上でとっても大事なのですが
いかんせんとっつきにくいと思います。実際私の周りでもpython勉強しだして、詰まり始めるのがこのあたりでした。
とりあえず日本語にしてみると
そのまんま、日本語にしてしまうと以下のような意味になります。
iterable
反復可能なもの
iterator
反復するもの
generator
生成するもの
yield
生成する
日本語にすると、なんとなーく意味がぼんやりと分かるかも・・・・って感じ。
サンプル
で、実際にコードで表現できないと意味ないので、わかりやすい説明してるところ
とかないのかなと探したら、以下を発見。
stackoverflow の python カテゴリの説明は明確で丁寧なのが多いですね。
(まさに zen of python の精神)
とてもいい情報だったので、自分用のメモとして以下のサンプルつくりました。
# ---------------------------------------------------------- # [元ネタ] # https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python/231855#231855 # # 以下は、元ネタのページ内の記載を自分なりに理解した内容メモです。 # ---------------------------------------------------------- # ---------------------------------------------------------- # 前書き # -------------- # yield が何を行っているのかを理解するためには # まず generator が何なのかを理解する必要があります。 # generator を理解するには、iterable の理解が必要です。 # てことで、generator の前に iterable から理解していきます。 # ---------------------------------------------------------- # ---------------------------------------------------------- # Iterable # -------------- # list を生成すると、1つずつ要素を読み出すことが出来ます。 # 一つずつ、要素を読み出すことを「イテレーション」といいます。 # ---------------------------------------------------------- list01 = list(range(5)) for i in list01: pr('list-item', i) # ---------------------------------------------------------- # list01 は、iterable です。 # リスト内包表記(list comprehension)を使ったとき # リストが生成され、これも iterable なものとなります。 # ---------------------------------------------------------- list02 = [x * 2 for x in range(5)] for i in list02: pr('list-item(list comprehension)', i) # ---------------------------------------------------------- # for ... in ... は、iterableなもの全てに使えます。 # (リストや文字列や辞書や集合やファイルなど・・・) # # このように iterable なオブジェクトはとても使いやすく便利 # なのですが、全ての値をメモリに保持してしまうという面も持っています。 # # 例えば、大量なデータを持つリストなどです。このような時 # 実際に全てのデータが必要では無い場合もあります。 # 利用しないデータの割合が多い場合、多くは不必要にメモリにロードされ # 空きメモリを圧迫します。このような場合に generator が登場します。 # ---------------------------------------------------------- # ---------------------------------------------------------- # Generators # -------------- # Generators は イテレータです。(iterator => iterate するもの) # でも、一回しかイテレートすることが出来ません。 # (つまり、一回全要素をループさせると終わり) # # これは、generatorが一度に全ての要素をメモリに持つのではなく # 必要な要素をその都度 (on the fly) 生成するからです。 # # なので、generator (generate するもの) となります。 # # generator を生成する場合、() で生成部分を囲みます。 # 先ほどのリスト内包表記を generator に変更する場合 # [] を () に変えるだけで generator になります。 # ---------------------------------------------------------- gen01 = (x * 2 for x in range(5)) pr('generator-object', gen01) for i in gen01: pr('generator-item(first-loop)', i) else: pr('first-loop', 'done') for i in gen01: pr('generator-item(second-loop)', i) else: pr('second-loop', 'done') # ---------------------------------------------------------- # Yield # -------------- # ここでやっと、yield の登場です。 # yield は return のような感じで使います。 # return は、値を返しますが、yield は generator を返します。 # # とりあえず、以下に yield のサンプルを記載します。 # 下記を実行すると、5回値を生成します。 # ---------------------------------------------------------- def create_generator(): yield 1 yield 2 yield 3 yield 4 yield 5 gen02 = create_generator() pr('generator-object', gen02) for x in gen02: pr('generator-item', x) # ----------------------------------------------------------------------- # 上記の create_generator 関数では yield が5回登場しています。 # なので、取得した generator をループすると 5回値が出力されます。 # # このように書くと、なんとなく分かるけど、なんとなく分からないって # なってしまうのが、yield のややこしいところです。 # # 大事なのが、以下の点です。 # 「関数内に yield が存在すると、python は 関数本体を実行せずに # generator を生成して、呼び元に返す。」 # # 内部に yield が利用されている関数は、呼ぶとすぐには実行されません。 # generator オブジェクトが生成されて、それが返ってきます。 # # なので、 # gen02 = create_generator() # と書くと、create_generator関数の中では yield があるので # python が内部で generator を生成して返してくれます。 # それが 変数 gen02 にセットされます。 # # generator は イテレートさせないと処理が進みません。 # 呼び出される度(つまり一回分イテレートされるたび)に、yield一つ分進み、次の # yield が見つかったタイミングで一時停止します。 # # なので、上のサンプルでは ループが一回進む度に yield も一つずつ進んでいって # 最終的に yield が5回分呼ばれたらループ終了となります。(StopIterationが発生します。) # # 一度使い切った generator は利用できないので、上記サンプルの gen02 を # 再度 for 文で利用しても、今度は一回もループされません。 # (yield 5 まで進んでしまっているので、次の yield がもうないため) # # yield の動き自体はシンプルなので、以下のように無限ループの中で yield すると # 止めない限り、永遠に値を生成するようにも出来ます。 # ------------------------------------------------------------------------ def gen_forever(): while True: yield datetime.now() gen03 = gen_forever() for i, d in enumerate(gen03): if i > 5: break pr('gen03', d.isoformat()) time.sleep(1)
実行すると、例として以下のようになります。
Connected to pydev debugger (build 171.4249.47) list-item=0 list-item=1 list-item=2 list-item=3 list-item=4 list-item(list comprehension)=0 list-item(list comprehension)=2 list-item(list comprehension)=4 list-item(list comprehension)=6 list-item(list comprehension)=8 generator-object=<generator object Sample.exec.<locals>.<genexpr> at 0x0000000004C3B7D8> generator-item(first-loop)=0 generator-item(first-loop)=2 generator-item(first-loop)=4 generator-item(first-loop)=6 generator-item(first-loop)=8 first-loop='done' second-loop='done' generator-object=<generator object Sample.exec.<locals>.create_generator at 0x0000000004BE48E0> generator-item=1 generator-item=2 generator-item=3 generator-item=4 generator-item=5 gen03='2017-05-02T18:46:34.268324' gen03='2017-05-02T18:46:35.268381' gen03='2017-05-02T18:46:36.268438' gen03='2017-05-02T18:46:37.268496' gen03='2017-05-02T18:46:38.268553' gen03='2017-05-02T18:46:39.268610' Process finished with exit code 0
サンプルのソースコードは以下でも見れます。
以下、参考にした情報です。
下記のページには、こんな記事よりももっと詳しく分かりやすく記載されています。
iterator - What does the "yield" keyword do in Python? - Stack Overflow
Pythonのジェネレータ、コルーチン、ネイティブコルーチン、そしてasync/await | プログラミング | POSTD
- 今回記載した内容の発展系として ジェネレータベースのコルーチンとネイティブコルーチンがあり、それについても触れられています。
- python 3.5 で追加された async/await についても記載あり。(C#やってる人にはおなじみのやつです。)
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場