毎日Learning

学んだことを共有します

エキスパート Python プログラミング メモ Vol.2

電車移動中にiPhoneから投稿します。
やっぱ本を読むのは電車だよね。

2.2 イテレータとジェネレータ

イテレータについては、割愛。

2.2.1 ジェネレータ

ジェネレータは今まで使ったことが無かったが、今回、エキPyを読んで多用することになりそう。

Python 2.2から提供されているジェネレータを使用すると、一連の要素を返す関数をシンプルかつ効率的に、エレガントな書き方で実装することができます。

シンプルかつエレガントとは、何とも魅力的だ。

>>> def fibonacci():
...     a,b = 0, 1
...     while True:
...         yield b
...         a, b = b, a+b
...
>>> fib = fibonacci()
>>> next(fib)
1
>>> next(fib)
1
>>> next(fib)
2
>>> [next(fib) for i in range(10)]
[3, 5, 8, 13, 21, 34, 55, 89, 144, 233]

一見、無限ループしそうで大丈夫!って思うコードだが、これがジェネレータの書き方なのか。
イテレータを簡単に実装する方法のように思える。

ループ処理やシーケンスを返す関数を実装するときには、まずジェネレータの利用を検討すべきです。

とある。おそらく、今まで、get_list()なんて呼び出して、リストに結果を格納して返却させるような関数のほとんどがジェネレータに置き換えることができるのだろう。

また、ジェネレータは複雑な処理を分解するのにも役立ちます。複数のデータ郡を使用するような、データ変換アルゴリズムの効率が向上します。それぞれのデータ郡を1つのイテレータとして実装し、高レベル関数の中にそれらを組み込むことで、巨大で、読みにくい関数になることを防ぐことができます。

ですと。これは超興味深い。

>>> def power(values):
...     for value in values:
...         print('powering %s' % value)
...         yield value
...
>>> def adder(values):
...     for value in values:
...         print('adding to %s' % value)
...         if value % 2 == 0:
...             yield value + 3
...         else:
...             yield value + 2
...
>>> elements = [1, 4, 7, 9, 12, 19]
>>> res = adder(power(elements))
>>> next(res)
powering 1
adding to 1
3
>>> next(res)
powering 4
adding to 4
7
>>> next(res)
powering 7
adding to 7
9

ジェネレータを使わないで同じ結果を得る場合、どのようなコードになるのだろう。
ちょっと書いてみた。

>>> def power(value):
...     print('powering %s' % value)
...     return value
...
>>> def adder(value):
...     print('adding to %s' % value)
...     if value % 2 == 0:
...         return value + 3
...     else:
...         return value + 2
...
>>> elements = [1, 4, 7, 9, 12, 19]
>>> for value in values:
...     adder(power(value))
...
powering 1
adding to 1
3
powering 4
adding to 4
7
powering 7
adding to 7
9
...

まあ、同じような結果を得られた。
もひとつ考慮が足りない気もするが…。

データではなく、コードをシンプルに保つ
一度に1つの結果を算出する複雑な関数よりも、シーケンス上で動作可能な、シンプルな関数をたくさん作るほうが良い手法と言えます。

とのこと。確かに後者のコードだとシーケンス上で動作可能な関数ではないよね。
しかも、毎回ループの中で関数を呼び出してるから、関数を呼び出すコストがかかる分、ジェネレータのほうが処理が速そうだ。