今さらPython3 (30) - ジェネレータなど
4章は盛りだくさん?まだまだ続きます。
- 作者: Bill Lubanovic,斎藤康毅,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/12/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
ジェネレータ
ちょっと前にタプル(tuple)の内包表記はなくて、ジェネレータになるよという話があったけど、それの続きと思えばOKかな?
>>> sum(range(1,101)) 5050
Python2.7を触っている時も、ジェネレータという存在を意識したことがなかったけど、2.xの時はrange()はリストを返していたけど、3.xになってからはジェネレータを返しますと。
念のため、別のターミナルウィンドウを開いて、Python2.7の動きを確認。
$ python Python 2.7.4 (v2.7.4:026ee0057e2d, Apr 6 2013, 11:43:10) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> range(1,101) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100] >>>
確かにリストを返しているね。もう1回Python3.4.3に戻って同じ事を試す。
>>> type(range(1,101)) <class 'range'> >>> range(1,101) range(1, 101)
前回タプルで内包表記を作ったつもりで、実際出来たのがジェネレータだった訳だけど、内包表記で収まらないような長い場合は、ジェネレータ関数を書くと。
>>> def my_range(first=0, last=10, step=1): ... number = first ... while number < last: ... yield number ... number += step ... >>> my_range <function my_range at 0x1020749d8> >>>
returnではなくてyieldを使っているところが違い。
>>> ranger = my_range(1,5) >>> ranger <generator object my_range at 0x102073e58>
rangerはジェネレータオブジェクトになった。もし、my_rangeが通常のreturnを使った関数なら、戻り値(この場合はint)が入るはず。
>>> for x in ranger: ... print(x) ... 1 2 3 4
my_range()の中を見ると、whileの中でyield numberというのがあるので、1, 2, 3, 4という値が保持される。for文でrangerの中をグルグルすると、保持された値が吐き出されていると理解すれば良さそう。
>>> for x in ranger: ... print(x) ... >>>
でも、もう1回同じ事をやると、何も出なくなるのは、以前確認済。
>>> ranger2 = [x for x in my_range(1,10)] >>> ranger2 [1, 2, 3, 4, 5, 6, 7, 8, 9]
再利用するなら、リストに渡しておけば良いね。
デコレータ
入力として関数の1つを取り、別の関数を返す関数ーーー禅問答だね。実例から理解しよう。
>>> def document_it(func): ... def new_function(*args, **kwargs): ... print('Running function:', func.__name__) ... print('Positional arguments:', args) ... print('Keyword arguments:', kwargs) ... result = func(*args, **kwargs) ... print('Result:', result) ... return result ... return new_function ...
まずはこれを作りましたよ。最後の行を見る限り、戻り値は関数そのもの。__name__は関数の名前、argsとkwargsは引数を拾っている。それをresultという変数に返して、最後のreturnにも渡しているという予想。
>>> def add_ints(a, b): ... return a + b ... >>> add_ints(3, 5) 8 >>> cooler_add_ints = document_it(add_ints) >>> cooler_add_ints(3,5) Running function: add_ints Positional arguments: (3, 5) Keyword arguments: {} Result: 8 8 >>>
最初に見たときは、何だこれ?と思ったけど、少し時間をおいたら意味が分かってきたかも。ここは大事なところのような気がするので、受け流さずに一歩ずつ。
- 変数cooler_add_intsは、document_itにadd_intsを受け渡した関数new_functionがアサインされる。
- cooler_add_ints(3,5)を実行するということは、new_function(3,5)が実行されるのに等しい。
- new_functionの中参照しているfunc.__name__は、add_ints()のこと。
- positional argumentは、*argsだから(3,5)が返る。
- resultはfunc(*args, *kwargs)の結果が入るので、3+5=8が返る
- だめ押しでprint(result)しているから8が表示される。
そゆことね。90%ぐらい分かった。(100%じゃないんかい!)
>>> @document_it ... def add_ints(a,b): ... return a+b ... >>> add_ints(4,6) Running function: add_ints Positional arguments: (4, 6) Keyword arguments: {} Result: 10 10
Pythonにも@が出てくるのかと身構えたけど、最初のパターンで書くより楽だよね。
>>> def square_it(func): ... def new_function(*args, **kwargs): ... result = func(*args, **kwargs) ... return result * result ... return new_function ... >>> @document_it ... @square_it ... def add_ints(a,b): ... return a + b ... >>> add_ints(4,7) Running function: new_function Positional arguments: (4, 7) Keyword arguments: {} Result: 121 121
デコレータは、defの直上のものから遡って実行されるらしい。上の例だと、squre_it()のあとにdocument_it()が実行されたということになるのかな?
>>> @square_it ... @document_it ... def add_ints(a,b): ... return a + b ... >>> add_ins(4,7) 11 >>> add_ints(4,7) Running function: add_ints Positional arguments: (4, 7) Keyword arguments: {} Result: 11 121 >>>
だから順番を入れ替えると、document_it()が先に処理されるので、resultに返る値が4+7=11。最後の戻り値のところは11*11=121になっていると。document_it()みたいなのはデバッグ目的で使うのは分かるけど、square_it()的なのはどういう用途に使うのかは、まだよく見えないかも。
(つづく)