python作为可选的生成器

时间:2017-12-06 21:28:55

标签: python generator

假设我有一个功能,我想有选择返回结果。这很容易编码:

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_returnFalse,则foo_gen只是一个没有返回值的函数,is_returnTrue 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]的行为吗?

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_genis_return为真时所做的事情是完全无关紧要的。它只是迭代生成器而不做任何事情。这是特别愚蠢的,因为在这种情况下,除了打印之外,发生器本身不会做任何事情。

最好让函数API保持一致:要么总是返回一个生成器,要么永远不会。更好的想法是只有两个函数foo_gen即生成器,print_gen或无条件打印它的东西。如果您想要生成器,请致电foo_gen。如果您只想打印它,请改为调用print_gen,而不是将“flag”参数传递给foo_gen

关于你最后的评论:

  

但这并不是那么好,而且对API的用户来说很困惑,因为他们不希望迭代任何东西来发生副作用。

如果API指定函数返回生成器,则用户应该必须迭代它。如果API说它不返回生成器,则用户不应期望必须迭代它。 API应该只说一个或另一个,这将使用户清楚地知道会发生什么。更令人困惑的是有一个笨拙的API告诉用户他们必须传递一个标志来确定他们是否得到了一个生成器,因为这会使用户的期望变得复杂。

答案 1 :(得分:1)

  

因此,假设is_returnFalse,那么foo_gen只是一个   函数没有返回值且is_returnTrue foo_gen时   是一个发电机

你的假设是错误的。 is_return无法确定您的函数是否为生成器。仅存在yield表达式确定表达式在函数调用时是否可达,无关紧要。

所以你可能想要坚持第一种返回列表的方法,在我看来这种方法不那么容易混淆和维护。