💭

pythonのジェネレータは、yieldの有無ではなく、「yieldで返したか否か」で決まる。

2021/09/15に公開

次の記事を読んだ。

細かすぎて伝わりにくい、Pythonの本当の落とし穴10選+α - Qiita

総評としては、機構を把握せず(何故そういう挙動になるのかを理解せずに)に表面から入ろうとするとそうなるかもなあ、というものが多い。

なかでも気になったのがこれ。

(このように、ジェネレータ関数は def で定義します。これは通常の関数と同じですよね。違いは関数定義の中に yield があるかどうかです。

表面上はそう視えるが、実際には若干違う。

import dis

def f1():
    yield 1

def f2():
    return 1

print(dis.dis(f1))
print(f1)
print(f1())
print(dis.dis(f2))
print(f2)
print(f2())

上記のスクリプトを実行してみれば明白だが、それぞれのバイトコードと、インスタンスの種類を確認してみよう。

  4           0 LOAD_CONST               1 (1)
              2 YIELD_VALUE
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE
None
<function f1 at 0x7fece8a23040>
<generator object f1 at 0x7fece885c970>
  7           0 LOAD_CONST               1 (1)
              2 RETURN_VALUE
None
<function f2 at 0x7fece8869820>
1

f1/f2どちらもfunctionとして定義されている。returnはどちらもRETURN_VALUEのバイトコードになっている。つまりどっちも関数である。

違いはなにか。YIELD_VALUE/POP_TOP だ。

yieldは「ジェネレータ関数かどうかを決定する構文」ではなくて、「関数の実行を動的に中断して関数内のコンテキストを保存し、値を返す機能」だと考えるべきだろう。 それがたまたまgeneratorオブジェクトであるというだけだ。

他の言語から入ってきた人が違和感をもつのは仕方がない。 だが、小生としては、「わざわざジェネレータ関数という仕様を追加せずに、ジェネレータ機能を実装した」ことに敬意を表するとともに、小生がpythonを教えるとしたら、ここは強調するつもりであることを明記しておく。

Discussion