今さらPython3 (30) - ジェネレータなど

4章は盛りだくさん?まだまだ続きます。

入門 Python 3

入門 Python 3

ジェネレータ

ちょっと前にタプル(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()的なのはどういう用途に使うのかは、まだよく見えないかも。

(つづく)