假设我有一个功能,我想有选择返回结果。这很容易编码:
def foo(N, is_return=False):
l = []
for i in range(N):
print(i)
if is_return:
l.append(i)
if is_return:
return l
但现在让我说我希望这个功能成为一个发电机。我会写这样的东西:
def foo_gen(N, is_return=False):
for i in range(N):
print(i)
if is_return:
yield i
因此,假设is_return
为False
,则foo_gen
只是一个没有返回值的函数,is_return
为True
foo_gen
时一个生成器,我希望它有两个不同的调用:
In [1]: list(foo_gen(3, is_return=True))
0
1
2
Out[2]: [0, 1, 2]
当它是一个生成器并且你必须遍历生成的值时,并且:
>>> In [2]: foo_gen(3)
0
1
2
因为它不是一个发电机而且它只是有它的副作用而你不必迭代它。但是,后一种行为并不适用于只返回发电机。你可以从中得到任何东西:
In [3]: list(foo_gen(3, is_return=False))
0
1
2
Out[3]: []
但这并不是一件好事,而且对API的用户来说也很困惑,他们不希望迭代任何东西来发生副作用。
无论如何都要在函数中表达In [2]
的行为吗?
答案 0 :(得分:3)
要做到这一点,你需要将foo_gen
包装在另一个函数中,该函数返回生成器或迭代它自己,如下所示:
def maybe_gen(N, is_return=False):
real_gen = foo_gen(N)
if is_return:
for item in real_gen:
pass
else:
return real_gen
def foo_gen(N):
for i in range(N):
print(i)
yield i
>>> list(maybe_gen(3))
0
1
2
[0, 1, 2]
>>> maybe_gen(3, is_return=True)
0
1
2
>>>
原因是函数中任何地方出现yield
都会使它成为生成函数。没有办法让一个函数在通话时决定它是否是一个生成器函数。相反,您必须具有非生成器函数,该函数在运行时决定是否返回生成器或其他内容。
那就是说,这样做很可能不是一个好主意。你可以看到maybe_gen
在is_return
为真时所做的事情是完全无关紧要的。它只是迭代生成器而不做任何事情。这是特别愚蠢的,因为在这种情况下,除了打印之外,发生器本身不会做任何事情。
最好让函数API保持一致:要么总是返回一个生成器,要么永远不会。更好的想法是只有两个函数foo_gen
即生成器,print_gen
或无条件打印它的东西。如果您想要生成器,请致电foo_gen
。如果您只想打印它,请改为调用print_gen
,而不是将“flag”参数传递给foo_gen
。
关于你最后的评论:
但这并不是那么好,而且对API的用户来说很困惑,因为他们不希望迭代任何东西来发生副作用。
如果API指定函数返回生成器,则用户应该必须迭代它。如果API说它不返回生成器,则用户不应期望必须迭代它。 API应该只说一个或另一个,这将使用户清楚地知道会发生什么。更令人困惑的是有一个笨拙的API告诉用户他们必须传递一个标志来确定他们是否得到了一个生成器,因为这会使用户的期望变得复杂。
答案 1 :(得分:1)
因此,假设
is_return
为False
,那么foo_gen
只是一个 函数没有返回值且is_return
为True
foo_gen
时 是一个发电机
你的假设是错误的。 is_return
无法确定您的函数是否为生成器。仅存在yield
表达式确定表达式在函数调用时是否可达,无关紧要。
所以你可能想要坚持第一种返回列表的方法,在我看来这种方法不那么容易混淆和维护。