結論から書くとそんなことはなかったです。
ということで、Generator関数について勘違いしている部分があったので備忘録を兼ねて。
普通の関数
これは何の変哲も無い普通の関数です。ちゃんと1が返ってきます。
>>> def a():
... return 1
...
>>> a() # 実行結果は当然1
1
>>> type(a())
<class 'int'>
Generator関数
Generator関数はyieldキーワードで値を送出することでメモリ効率のいいiterableを作り出せます。
>>> def b():
... yield 1
...
>>> b() # 実行結果は当然generator
<generator object b at 0x106db82b0>
>>> next(b())
1
>>> type(b())
<class 'generator'>
別の関数やメソッドに渡したり、使いまわしたりする際には多少気をつかう必要がありますが、個人的にはかなり使います。
ここからが本題
さて、ここで下記は普通の関数、Generator関数どちらでしょうか。(実際にこんな感じのコードを書いてました)
また、実行結果はどうなるでしょうか。
def c(n):
if n:
return 1
else:
yield 1
実際に動かしてみました。
>>> def c(n):
... if n:
... return 1
... else:
... yield 1
...
>>> type(c(0)) # `yield 1`なのでまぁそうなるよね的な動き
<class 'generator'>
問題はこちら。
return 1
だけど返り値は1
じゃない!!
>>> type(c(1)) # `return 1`だけど1が返ってきていない
<class 'generator'>
>>>
>>> c(1) + 1 # 当然数値型が要求される式で使うと怒られます
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'generator' and 'int'
ちなみに yield from
を使った場合でももちろん同じです。
>>> def e(n):
... if n:
... return 1
... else:
... yield from (i for i in range(9))
...
>>> type(e(0))
<class 'generator'>
>>> type(e(1))
<class 'generator'>
ということで、 return 1
って書いても1
が返ってこないこともあるよという話でした。
この例ではわかりやすいように1
を返しています。
が、「listかgeneratorを返す」関数を作り、その返り値を他の関数に渡すなどしていると、原因とエラーの箇所が離れてしまう非常に面倒なバグ
になります。
なので、正直なところ返り値の型が大きく違う関数はあまり書かないほうがいいのかなと思います。
おまけ
ちなみに実際にこういう処理が欲しい時は、こんな感じで関数自体を分けるするのがいいと思います。
>>> def _f():
... return 1
...
>>> def _g():
... yield 1
...
>>> def d(n):
... if n:
... return _f()
... else:
... return _g()
...
>>> d(0)
<generator object g at 0x106db8410>
>>> d(1)
1
>>> d(1) + 1
2