Python
generator
SENSYDay 9

Pythonの「return 1」は必ず1を返す。そう思っていたときが僕にもありました。

More than 1 year has passed since last update.

結論から書くとそんなことはなかったです。

ということで、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