从调用生成器的函数返回还是让出?


30

我有一个生成器generator,还有一个便捷的方法- generate_all

def generator(some_list):
  for i in some_list:
    yield do_something(i)

def generate_all():
  some_list = get_the_list()
  return generator(some_list) # <-- Is this supposed to be return or yield?

应该generate_all return还是yield?我希望两种方法的用户都使用相同的方法,即

for x in generate_all()

应该等于

some_list = get_the_list()
for x in generate(some_list)

2
有一个原因可以使用。对于此示例,返回效率更高
Mad Physicist

1
这让我想起了我曾经提出过的一个类似问题:“可迭代的收益”与“返回可迭代的”。虽然不是专门针对生成器,但它基本上与生成器相同,并且迭代器在python中非常相似。同样,答案所建议的比较字节码的策略也可以在这里使用。
PeterE

Answers:


12

发电机是懒惰的,所以评估returnyield将表现不同,当你调试代码,或者抛出一个异常。

不管return发生什么异常,您generator一无所知generate_all,这是因为在generator真正执行时,您已经离开了该generate_all功能。随着yield在那里它会generate_all在回溯。

def generator(some_list):
    for i in some_list:
        raise Exception('exception happened :-)')
        yield i

def generate_all():
    some_list = [1,2,3]
    return generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
      8     return generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-3-b19085eab3e1> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

如果正在使用yield from

def generate_all():
    some_list = [1,2,3]
    yield from generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
      8     yield from generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-4-be322887df35> in generate_all()
      6 def generate_all():
      7     some_list = [1,2,3]
----> 8     yield from generator(some_list)
      9 
     10 for item in generate_all():

<ipython-input-4-be322887df35> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

但是,这是以性能为代价的。附加的生成器层确实有一些开销。因此return通常会比yield from ...(或for item in ...: yield item)快一点。在大多数情况下,这无关紧要,因为您在生成器中所做的任何事情通常都会影响运行时,因此额外的层将不会很明显。

但是,它yield还有一些其他优点:您不仅限于单个可迭代对象,还可以轻松产生其他项:

def generator(some_list):
    for i in some_list:
        yield i

def generate_all():
    some_list = [1,2,3]
    yield 'start'
    yield from generator(some_list)
    yield 'end'

for item in generate_all():
    print(item)
start
1
2
3
end

在您的情况下,操作非常简单,并且我不知道是否为此需要创建多个函数,可以轻松地使用内置函数map或生成器表达式来代替:

map(do_something, get_the_list())          # map
(do_something(i) for i in get_the_list())  # generator expression

两者应相同(除了发生异常时的一些区别)使用。而且,如果它们需要一个更具描述性的名称,那么您仍然可以将它们包装在一个函数中。

有多个帮助程序将非常常见的操作包装在内置的可迭代对象上,进一步的操作可以在内置itertools模块中找到。在这种简单的情况下,我仅会求助于这些情况,并且仅在非平凡的情况下编写您自己的生成器。

但是我认为您的实际代码更加复杂,因此可能不适用,但我认为如果不提及替代方案,这将不是一个完整的答案。


17

您可能正在寻找发生器委托(PEP380)

对于简单的迭代器,yield from iterable本质上只是的简化形式for item in iterable: yield item

def generator(iterable):
  for i in iterable:
    yield do_something(i)

def generate_all():
  yield from generator(get_the_list())

它非常简洁,还具有许多其他优点,例如能够链接任意/不同的可迭代对象!


哦,你的意思是list?这是一个不好的例子,不是真实的代码粘贴在问题中,我可能应该对其进行编辑。
hyankov

是啊-不要害怕,我很内疚,不会在第一次询问甚至可以运行示例代码..
ti7

2
第一个也可以是单线的:)。yield from map(do_something, iterable)甚至yield from (do_something(x) for x in iterable)
疯狂物理学家

2
“这一直是示例代码!”
ti7

3
仅当您自己在做其他事情而不只是返回新生成器时,才需要委派。如果仅返回新的生成器,则不需要委派。yield from除非您的包装器执行其他生成器y,否则这毫无意义。
ShadowRanger

14

return generator(list)做你想要的。但请注意

yield from generator(list)

是等效的,但是用generator完后有机会产生更多的价值。例如:

def generator_all_and_then_some():
    list = get_the_list()
    yield from generator(list)
    yield "one last thing"

5
我相信有之间的细微差别yield from,并return在发电机的消费者throws它的一个例外内-并与由堆栈跟踪影响其它操作。
WorldSEnder

9

在此特定情况下,以下两个语句在功能上似乎是等效的:

return generator(list)

yield from generator(list)

后者大约与

for i in generator(list):
    yield i

return语句返回您要查找的生成器。一个yield fromyield声明将你的整体功能到的东西,返回一个生成器,它通过你正在寻找的人。

从用户的角度来看,没有区别。然而,在内部,return由于没有封装generator(list)在多余的直通发生器中,因此可以说效率更高。如果您打算对包装后的生成器的元素进行任何处理yield,请当然使用某种形式。


4

你会的return

yielding *将导致generate_all()求值一个生成器本身,并且调用next该外部生成器将返回第一个函数返回的内部生成器,这不是您想要的。

* 不包括 yield from

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.