Python、はじめました

非プログラマーかつ私文卒リーマンの Pythonデビュー全記録

【SoloLearn】関数プログラミング-Generators

f:id:pokita:20180527162737p:plain

SoloLearn-Functional Programming-Generators

 

簡潔に説明すると、「通常の関数の、return(戻す)文をyield(生み出す)に置き換えたもの」

もう少し詳しく説明すると、通常の関数が引数の値を受け取って、唯一つの結果を戻す、という処理をするのに対し、ジェネレーター関数は、呼び出されると「一旦何らかの値を戻して、しばらく後に、その次点からの処理を再開する」というもの。

????

ジェネレーター関数は呼び出すたびに、その実行状況が「一時停止」され、停止している間は、「停止状況はどうだったか」「どこまで処理が進んだか」というステート情報を保持することができる。

 

このgenerator、iteratorの項目でも出てきたが、呼び出された回数を記録される関数として副作用のなさを目指す関数プログラミングには不適切な機能では?とか思ってしまう。

 

まずは第1項。

 

Unlike lists,they don't allow indexing with arbitrary indices, but they can still be iterated through with for loops.They can be created using functions and the yield statemante.

インデックスを使ってスライシングすることはできないけど、forループで回すことはできるよ、とのこと。

実際に回してみる。

 

def count_up():
  x = 0
  while True:
    yield x
    x += 1

 

for i in countup():
  print(i)
  if i == 5:
    break
>>>0
>>>1
>>>2
>>>3
>>>4
>>>5

 

書いていることはわかるんだけど、なんとなく違和感。for ループで回している変数iがgenerator関数に格納されるたび、generator関数はyieldで生み出されたものを吐き出す、ってこと?←☓

for構造についての理解がまずダメ。ここを見てからやり直し。

 

forループのおさらい。

forループで重要になるのが、for __代入ターゲット__ in __指定オブジェクト__:という構造において、指定オブジェクトの要素の数(n)と、0~n-1のindex番号を指定オブジェクトに入れた際に戻される代入ターゲットの値である。

要素の数はいくつ?index=[n]を指定したときに戻される値は?この2点。

 

python3.hatenadiary.jp

つまり、考えるべきはgenerator関数の要素の数とindex[n]の値である。

ん?generatorオブジェクトに要素なんてあるの?index対応してる???

結論から言えば、要素なんてないし、indexに対応してなんかいない。

 

def count_up():
  x = 0
  while True:
    yield x
    x += 1

len(count_up())

>>>TypeError: object of type 'generator' has no len()

count_up()[1]

>>>TypeError: 'generator' object is not subscriptable

 

indexや要素の数の話はあくまでも方便で、実の所は、オブジェクトが__next()__メソッドを実装しているか否か。オブジェクト指向にも関係してくる話なので、戦線が長くなりそうなので、『要素の数』『index』という話で簡潔にまとめてしまいました。

 

generator関数をgenerator関数たらしめているのは、"yield"という一文を入れているから。def function():の中にyieldを入れることで、その関数はgenerator関数としてコンパイルされ、Pythonに認識される。

generator”関数"と呼ばれているが、実際は、繰り返し呼び出されるたびに計算が行われ、そのそれぞれのステートにおいての計算結果の値を戻すリストのようなもの、と思った方がいいのかもしれない。

 

def func():
  x = 1
  return x

 

def generator():
  x = 0
  while True:
    yield
    x +=1

 

print(type(func))

>>><class 'function'>
print(type(generator))

>>><class 'function'>

 

func,generatorどちらも関数オブジェクトであることに変わりはないけど、

 

print(type(func()))

>>><class 'int'>
print(type(generator()))

>>><class 'generator'>

 

呼び出し結果において、そのオブジェクトのtypeは異なる。generator関数はfor構文の燃料とすることで、計算を繰り返し値を戻し続けるオブジェクトなのだ。

 

list等のiteratableオブジェクト→はじめからすべてitemが定義されているよ!(その個数も有限個)

generator関数オブジェクト→呼び出されるたびに計算が行われその結果をitemとしてyieldするよ!(その個数は無限個!)

 

上のコード例をもう少しわかりやすく書く。

 

def count_up():
  x = 0
  yield x #←シーケンス番号0の呼び出し結果
  x += 1

  yield x #←シーケンス番号1の呼び出し結果

 

for i in countup():
  print(i)
  if i == 5:
    break
>>>0
>>>1

 

forループの代入ターゲットiには、はじめcount_up関数の1回目の呼び出し結果のyieldまでが代入される。

generator関数のシーケンス番号nでindexした値=n+1回目の呼び出し結果

ということなので。print(i)により、代入ターゲットi=1回目の呼び出し結果=0が出力される。

 次に、代入ターゲットiには、count_up関数の2回目の呼び出し結果のyieldが代入される。generator関数内のステート情報は保存されているので、呼び出しは前回の続き#←以下から。

その次に、count_up関数を呼び出したときには、count_up関数は最終行まで進んでいるため、StopIterationが返されforループ処理が終わる。

という流れ。

 

次に第2項。

 

Due to the fact that they yield one item at a time, generators don't have the memory restrictions of lists.In fact, they can be infinite.

generator関数は一度に一つの要素しか生み出さないから、メモリの制限がなく、事実無限大である。

 

この項目のコードは簡単すぎるので省略。

 

最後に第3項。

 

Finite generators can be converted into lists by passing them as arguments to the list function.

有限なgenerator関数はlist関数の引数にすることでlistに変換できるよ。

ここで注目したいのが、有限なgenerator関数を作るための方法。generator関数に引数を定義し、その引数を内部のrangeオブジェクトに使うという方法.。

実際に回してみる。

 

def numbers(x):

  for i in range(x):

    if i %2==o:

      yield i

 

print(list(numbers(11)))

>>>[0,2,4,6,8,10]

 

numbers(11)はあくまでも、呼び出されるたびにrange(x)のシークエンス数が全部回されるまで計算をし値を吐き出す、という関数であり、そもそも要素は一つも含んでいない。それでもlist関数の引数に入れることで、number(11)をiterationで回した際に得られるすべての要素のリスト群が戻されるのだ。

number(11)自体はただiterオブジェクトなので、それだけではまともな値を返さない。

print(numbers(11))

>>>generator object numbers at 0x05227E40

返すのはtype名とaddressのみ。