ITの隊長のブログ

ITの隊長のブログです。Rubyを使って仕事しています。最近も色々やっているお(^ω^ = ^ω^)

pythonのyieldにハマったから、初心者なりに解明してみた

スポンサードリンク

AM 08:00

さーて、頼まれてたクローラー、サクサクつーくろ
( ・ω・)ノ オー

PM 01:00

ふーむ、あらかたscrapyで用意することできたわ
単純なクローラーだったらサクッて作れるね
( ^ω^)おっおっおつ

PM 01:15

でも、ちっと連続でクローリングするbotみたいなもの
作りたいから、もうちょいやってみよう。
そうだ、別関数を用意して回すのはどうかな?
( ・ω・)?

PM 01:30

英語・・・。んん?この「yield」ってなんやねん。
ふむふむ、ジェネレータがうんちゃらかんちゃら?
( ・ω・)?

まぁ、使わなければいいでしょ。

PM 08:00

( ・ω・)


( ;ω;)ブワッ


できなかったよ!!
なんかしらんけど、英語圏のQ&Aってだいたいが「yield」
使っているから、回避できへん!!


クッソ、こうなったら徹底的に理解してやる・・・・!!


1. 「yield」とは?

「yield、と言ったら、ジェネレータ」と考えたらダメらしい。
ちゃんとよく読まないでそう思っていたのは、私です。
すみません。


では?なんでしょう?

Pythonでは、関数定義の中にyield文があると、その関数定義は通常の関数を定義するのではなく、一種のコルーチンの記述のようになる。yield文を含む関数は、イテレータと同じインタフェースを持つ呼び出し可能オブジェクトを返す関数になる。ジェネレータの語は、「yield文を含む関数定義により定義された関数」と、それが返す「イテレータと同じインタフェースを持つ呼び出し可能オブジェクト」を、はっきりと区別せずに使われているが、ここでは、前者をジェネレータ、後者をイテレータと呼ぶ。
このイテレータは、ジェネレータの定義中の各yield文の所まで実行した状態を保存するスタックフレームを保持するオブジェクトであると考えることができる。イテレータのnext()が呼び出されると、Pythonは保存されたフレームを復帰し、次のyield文に到達するまで実行する。yield文の実行によりフレームは再び保存され、yieldの引数の値がnext()の呼び出し元に返される。


と、いうことらしい。


なるほど!わからん!!
( `・ω・)クワッ!

もう少し調べたところ
通常、関数は「return」を利用して、値を返却しますが
「yield」はそうではなく、処理を途中で止めて、保存しておく・・・?
ってことができるとか。

全然理解してないので、例で復習します。

def fruits():
	yield 'apple'
	yield 'orange'
	yield 'meron'

for i in fruits():
	print i


上のスクリプトを実行すると

apple
orange
meron


と、なる。
もうちょっと、わかりやすい内訳

def fruits():
	yield 'apple'
	yield 'orange'
	yield 'meron'

# 'apple'がでてくる
print fruits().next()
# 'orange'がでてくる
print fruits().next()
# 'meron'がでてくる
print fruits().next()


むずかしい。。。
まぁ、上をみたらわかると思うけど
yieldを使うと、その時点で関数(とは違うけど)の処理を中断します。
中断された処理は、next()で再開するようになるとか。


また、next()じゃなくとも、forループ文でも再開できる。
next()自体と、それ以外のメソッドを使って、繰り返し処理を行っている。


人によって、理解のイメージは違うが
ティッシュ箱だったり、冷凍野菜みたいなイメージを持っている人もいました。
冷凍野菜がわかりやすいかな?
一旦は冷凍保存して、使いたいときにチーン!して、取り出すみたいな。
※いや、わかりにくいか?

2. 何が便利なの?returnでいいじゃん。

そうだね。全く便利な感じがしないよね。
だけども、圧倒的理由が存在します。
隊長は今のところ、無駄を無くすってイメージで理解しています。


たとーえば

# yield
def yield_fruits():
	yield 'apple'
	yield 'orange'
	yield 'meron'

# normal function
def normal_fruits():
	list = ['apple', 'orange', 'meron']
	return list


上の関数(ジェネレータ?)と、下の関数はやること違えど
帰ってくる値は一緒です。


では、何が便利かというと
メモリの効率化が上げられます。
yieldのほうが効率が良い場合があるんです。


ここで、利用したい値が「apple」だけだとしましょう。
yieldの場合は、「apple」は1回めに帰ってくるので
それ移行はループさせなくてもOKです。


しかし、normal_functionの場合は、3つのlistが返却されてきます。
これが例えば、数十万、数百万のlistであればどうでしょう?
必要なもの以外返却されてては、メモリの消費がバカになりません。
こういった時にめっちゃ便利なんだとか。

3. イテレータ?ジェネレータ?

これもわかりませんでした。
yield触っていると、この2つの説明とよく出くわしました。
ついで、理解しようと思いました。

イテレータ?(´・ω・)?

イテレータ(英語: Iterator)とは、プログラミング言語において配列やそれに類似するデータ構造の各要素に対する繰返し処理の抽象化である。実際のプログラミング言語では、オブジェクトまたは文法などとして現れる。反復するためのものの意味で反復子(はんぷくし)と訳される。繰返子(くりかえし)という妙訳もある。


んーと、繰り返し処理のこと!
んで、iter()って組み込み使って、next()で次の要素を取得するってこと。
for文も実は、next()使ってました!

って感じかな(´・ω・)アッテルカナ?


でも、for文はイテレータではない。
for文はイテレータを使う場合は使うが、そうでない場合は
コンテナオブジェクト(__getitem__())を使うらしいので
そう括ることができないんだとさ。


なんとなく理解した。


ジェネレータ???(;´・ω・)???

f:id:aipacommander:20140615104505j:plain


これじゃないらしい。(あたりめーよ)
じゃあ!なんなの!!?

ジェネレータは、プログラムにおいて、数列の各要素の値などを次々と生成(ジェネレート)し他の手続きに渡す、という機能を持っている手続きである。値を渡す方法としては、コールバックのようにして他の手続きを呼ぶものもあれば、呼び出される度に次々と異なる値を返す関数であることもある。


えと、、、えっと・・・?
よくわからない。。。


とりあえず、キーワードがあるらしい。

  • 「ジェネレータはイテレータを作るツール
  • 「関数の途中で結果を返し、次に呼び出すときに処理を再開する」


まぁ、これなら動き方はだいたい理解できるかも。。。
そうすると、yieldはジェネレータってことになりますよね?


ではない・・・だと・・・!?(Wiki参照)


なんでや!!?
めっちゃ同じこといってますやん!!?


とりあえず、この点に関してはシカトします。よくわからん。
今のところは、「yield使うと、ジェネレータ使えます」ってことで。


うーむ。仕様についてはいまいちピンときていないけど
まぁ、使い方はわかったかな。


以上
それじゃ、頑張ろ( ・ω・)つ