ジェネレーターを呼び出す関数から戻るか、それとも譲りますか?


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 returnyield?私は両方の方法のユーザーにそれを同じように使用して欲しい、すなわち

for x in generate_all()

等しいはずです

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

2
どちらかを使用する理由があります。この例の場合、リターンはより効率的です
Mad Physicist

1
:これは私がかつて提起同様の質問を思い出させる「(反復可能)ITERを返す」対「反復可能からの収量」。ジェネレータについては特に触れていませんが、基本的にはジェネレータと同じで、イテレータはPythonでかなり似ています。また、回答で提案されているバイトコードを比較する方法もここで役立ちます。
PeterE

回答:


12

ジェネレータは、レイジー評価そうですreturnか、yieldあなたのコードをデバッグするか、例外がスローされた場合しているときに異なる動作をします。

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

どちらも使用するために(例外が発生したときのいくつかの違いを除いて)同一である必要があります。また、よりわかりやすい名前が必要な場合でも、それらを1つの関数でラップできます。

組み込みの反復可能オブジェクトに対する非常に一般的な操作をラップするヘルパーが複数あり、さらに他のヘルパーは組み込み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)
Mad Physicist

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と思います。returnthrows
WorldSEnder

9

次の2つのステートメントは、この特定のケースでは機能的に同等であるように見えます。

return generator(list)

そして

yield from generator(list)

後者はほぼ同じです

for i in generator(list):
    yield i

このreturnステートメントは、探しているジェネレータを返します。A yield fromまたはyieldステートメントは、あなたが探している1を通過するジェネレータを返し何かにあなたの全体の機能をオンにします。

ユーザーの観点からは、違いはありません。ただし、内部return的にgenerator(list)は、余分なパススルージェネレーターをラップしないため、間違いなく効率的です。ラップされたジェネレーターの要素に対して何らかの処理を行うことを計画している場合はyield、もちろん何らかの形式を使用してください。


4

あなたはreturnそうするでしょう。

yielding *はgenerate_all()ジェネレータ自体を評価し、nextその外側のジェネレータを呼び出すと、最初の関数によって返された内側のジェネレータが返されますが、これは望んでいません。

* 含まない yield from

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.