(2017.7.30)
Pythonのλ式はクロージャ (閉包; closure) を生成する。
ただ、 クロージャなので、レキシカルスコープ (lexical scope) になっている。
クロージャを生成 (定義) した際の変数 クロージャの型は class 通常の関数が値を返すには 関数ブロックのなかに, 字面として ジェネレイタオブジェクトに対して 注意したいのは, 関数が実行されるかジェネレイタになるかの条件、それから関数ブロックが一体いつ実行されるのか。
次の例では, つまり、実行時ではなく構文解析の段階で、文が実行される関数になるか、ジェネレイタを生成する関数になるか、が決まる。そうすると、最初の関数呼び出しが無意味のように感じるが、ジェネレイタに実引数を渡せるようにするため、こうなっているのだろう。でも、首尾一貫性も何もあったもんじゃない。
ジェネレイタを生成する関数呼び出しでは、ブロック内の文はまったく実行されない。
中の文は、ジェネレイタに対して最初の 以降は、 ジェネレイタは次のメソッドを持つ。
途中で処理を中断し、再開できるような構造は「コルーチン (coroutine)」または「継続 (continuation)」と呼ばれる。Pythonのジェネレイタは、まさにコルーチン。継続はもっと強力に、どこからでも再開できるようなもの。See Coroutine vs Continuation vs Generator - Stack Overflow
ジェネレイタは 実行結果:
ジェネレイタオブジェクトの 一度も 例として、フィボナッチ数を返す処理を作ってみる。
何も考えずに書くと、nが大きくなると急激に遅くなる。
続いて、自作のイテレイタ. 値をメモしつつ全部生成する。Python って値を生成せずにイテレイタを進めることができない。
イテレイタオブジェクトは内部状態を持てるが、 イテレイタと同じ処理をジェネレイタで書き直す。メソッドを跨がないので、コードがずいぶんコンパクトになる。
_ラムダ式, クロージャ
lambda 仮引数:
の後ろに改行を入れられないので、式一つの関数しか作れない。制限が強すぎる。
[10, 20, 30]
x
がクロージャに閉じ込められ、呼び出される場所の変数ではなく、定義した場所の変数が使われる。変数セットなどの「環境」がクロージャに保存されている。
'function'
で、def文による関数と同じ。持っているメソッドも完全に同じ;
>>> dir(simple_fun)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
_ジェネレイタ
return
による。あと例外による脱出もあるが。
yield
があるときは、その関数の呼び出しでは、文が実行されずに, ジェネレイタ generator が生成される。
hoge
fuga
__next__()
メソッドを呼ぶと, 関数ブロックが実行され, yield
式の場所でいったん実行が一時停止され、関数から戻る。__next__()
でその直後から再開される。
関数呼び出しになるかジェネレイタ生成式になるか
yield
式は決して実行されない。にも関わらず simple_gen()
でジェネレイタが生成される。
__next__
を呼び出したときに初めて実行され, 関数ブロックの始まりから最初の yield
までが実行される。ジェネレイタからの戻り値は yield
式の引数。そこで、いったん実行が中断される。「環境」が保存される。
__next__()
で yield式の直後から再開される。yield
以外で関数ブロックから抜けると StopIteration
例外が発生する
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
__iter__
と __next__
を持っており、for文にイテレータとして渡すことができる。
コルーチン
__next__()
で再開できるが、その時に外からジェネレイタの中に値を与えることもできる。__next__()
の代わりに send()
メソッドを使う。ジェネレイタの中で再開されたときに、yield式の値になる。
hoge
20
__next__()
で、関数の最初から最初のyield
まで実行される。この例では __next__()
の戻り値は, "hoge"
.
send()
メソッドで値を投げ込むことができる。この呼び出しでジェネレイタ内部で実行が再開される。外側のsend()
の戻り値は、ジェネレイタ内の, 次のyield
に与えられた引数。
__next__()
を呼び出していない状態で send()
すると、TypeError が発生する。うーん。
_関数、イテレイタ、ジェネレイタで同様の処理
fib(n - 2)
, fib(n - 1)
の無駄がないので、速度は上のよりは圧倒的。
__next__()
でつど脱出するため、何でもインスタンス変数で持たないといけない。self
まみれになる。