2017年9月17日日曜日

Pythonのyield構文

Pythonのyield構文について、どういう動作をしているのか簡単に調べてみました。Python 3.5で調べています。

forループで使用


3回yieldで返す関数genを作成し、forループで値を取得してみました。
1
2
3
4
5
6
7
8
9
def gen():
    yield 1
    yield 3
    yield 2
    
g = gen()
for v in g:
    print(v)
print("End")
実行すると、
1
3
2
End
が得られました。yieldで返した値が順に表示されます。

次に、無限に出力できるようにしてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def gen():
    x = 0
    while True:
        x += 1
        yield x

g = gen()
for v in g:
    print(v)
print("End")
実行すると、1, 2, 3,...と順に1ずつ値を増やしながら停止するまで無限に整数を出力し続けます。

nextを使用


forループを使わず、1回ずつ呼び出してみます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def gen():
    yield 1
    yield 3
    yield 2
    
g = gen()
try:
    c = 1
    print(next(g))
    c = 2
    print(next(g))
    c = 3
    print(next(g))
    c = 4
    print(next(g)) # raise a StopIteration exception
    c = 5
    print(next(g))
    c = 6
except StopIteration:
    print("End")
print("c =", c)
実行結果は、
1
3
2
End
c = 4
となります。yieldが返すことが出来るのは3回だけなので、4回目のnextで例外が発生します。

次に、グローバル変数aの値を変更・参照しながら、gen()を呼び出してみます。

 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
a = 0
def gen():
    c = 1
    yield c + a
    c += 2
    yield c + a
    c -= 1
    yield c + a
    
g = gen()
try:
    c = 1
    print(next(g))
    a = 10; c = 2
    print(next(g))
    a = 100; c = 3
    print(next(g))
    c = 4
    print(next(g)) # raise a StopIteration exception
    c = 5
    print(next(g))
    c = 6
except StopIteration:
    print("End")
print("c =", c)
実行結果は、
1
13
102
End
c = 4
となります。nextを呼び出す前にaを変更していますが、反映されていますね。cは固定値ではないですが、正しく前の値を覚えてくれています。

動作の仕組みは?


内部の仕組みを調べたわけではありませんので、以下は、事実と異なる可能性が高いのでご注意を。

上のコードのように、nextを呼び出すとgenが実行され、yieldに到達すると、yieldで指定されている値を返す直前に、yieldの次に実行すべき行番号や関数内でのみ使われている変数が保存されているのでしょう。次にnext経由で当該関数が呼び出されると、保存していた行番号や変数の値を復元し、続きを実行しているのだと思われます。

0 件のコメント :