it-swarm-ja.tech

"yield"キーワードは何をするのですか?

Pythonでのyieldキーワードの使用は何ですか?それは何をするためのものか?

例えば、私はこのコードを理解しようとしています 1

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

そしてこれが呼び出し側です。

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

メソッド_get_child_candidatesが呼び出されるとどうなりますか?リストは返されますか?単一の要素?また呼ばれますか。その後の通話はいつ停止しますか。


1.このコードはJochen Schulz(jrschulz)によって書かれました。彼は距離空間のための素晴らしいPythonライブラリを作りました。これは完全なソースへのリンクです: モジュールmspace

9208
Alex. S.

yieldの機能を理解するには、generatorsが何であるかを理解する必要があります。そして、ジェネレータを理解する前に、iterablesを理解する必要があります。

イテラブル

リストを作成すると、そのアイテムを1つずつ読むことができます。アイテムを1つずつ読み取ることを反復と呼びます。

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylistiterableです。リスト内包表記を使用すると、リストが作成されるため、反復可能です。

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

"for... in..."で使用できるものはすべて反復可能です。 listsstrings、ファイル...

これらのイテラブルは必要なだけ読むことができるので便利ですが、すべての値をメモリに保存します。多くの値がある場合、これは必ずしも必要なものではありません。

発電機

ジェネレーターはイテレーターで、一種の反復可能で、1回しか反復できません。ジェネレーターはすべての値をメモリに保存するわけではありません、その場で値を生成します

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

()の代わりに[]を使用した以外はまったく同じです。ただし、ジェネレーターは一度しか使用できないため、cannotfor i in mygeneratorを2回実行します。0を計算し、それを忘れて1を計算します、4の計算を1つずつ終了します。

産出

yieldは、returnと同様に使用されるキーワードですが、関数がジェネレーターを返すことを除きます。

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

ここでは役に立たない例ですが、関数が巨大な値のセットを返すことを知っていると便利です。

yieldをマスターするには、関数を呼び出すときに関数本体に記述したコードが実行されないことを理解する必要があります。関数は、ジェネレーターオブジェクト、これは少し注意が必要です:-)

その後、forがジェネレーターを使用するたびに、コードは中断したところから続行します。

今、難しい部分:

forが関数から作成されたジェネレーターオブジェクトを初めて呼び出すと、関数のコードを最初からyieldに達するまで実行し、ループの最初の値を返します。次に、他の各呼び出しは、関数に記述したループをもう一度実行し、返される値がなくなるまで次の値を返します。

関数が実行されると、ジェネレーターは空と見なされますが、yieldにヒットしなくなります。ループが終了したか、または"if/else"をもう満たさないことが原因である可能性があります。


コードの説明

ジェネレータ:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

発信者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

このコードには、いくつかのスマートパーツが含まれています。

  • ループはリスト上で反復しますが、ループの反復中にリストが展開します:-)無限ループになる可能性があるため、少し危険な場合でも、これらのネストされたデータをすべて簡単に調べることができます。この場合、candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))はジェネレーターのすべての値を使い果たしますが、whileは同じジェネレーターオブジェクトを作成し続け、同じノードに適用されないため、以前のものとは異なる値を生成します。

  • extend()メソッドは、反復可能なオブジェクトを期待し、その値をリストに追加するリストオブジェクトメソッドです。

通常、リストを渡します:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

ただし、コードではジェネレーターを取得します。

  1. 値を2回読み取る必要はありません。
  2. たくさんの子供がいて、それらすべてをメモリに保存したくない場合があります。

Pythonはメソッドの引数がリストであるかどうかを気にしないので機能します。 Pythonはイテラブルを想定しているため、文字列、リスト、タプル、ジェネレーターで動作します!これはカモタイピングと呼ばれ、Pythonがとてもクールな理由の1つです。しかし、これは別の質問です。

ここで停止するか、少し読んでジェネレーターの高度な使用法を確認してください。

発電機の消耗を制御する

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

注:Python 3の場合、useprint(corner_street_atm.__next__())またはprint(next(corner_street_atm))

リソースへのアクセスの制御など、さまざまなことに役立ちます。

Itertools、あなたの親友

Itertoolsモジュールには、イテラブルを操作するための特別な関数が含まれています。発電機を複製したいですか? 2つのジェネレーターをチェーンしますか?ネストされたリストの値をワンライナーでグループ化しますか? Map / Zip別のリストを作成せずに?

その後、ただimport itertools

例? 4頭の競走馬が到着する可能性のある注文を見てみましょう。

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

反復の内部メカニズムを理解する

反復は、イテラブル(__iter__()メソッドの実装)およびイテレータ(__next__()メソッドの実装)を意味するプロセスです。反復可能オブジェクトは、反復子を取得できるオブジェクトです。イテレータは、イテラブルを反復処理できるオブジェクトです。

forループがどのように機能するか については、この記事で詳しく説明しています。

13704
e-satis

理解へのショートカットyield

yieldステートメントを含む関数を見つけたら、この簡単なトリックを適用して、何が起こるかを理解します。

  1. 関数の先頭にresult = []行を挿入します。
  2. yield exprresult.append(expr)に置き換えます。
  3. 関数の下部にreturn result行を挿入します。
  4. イェイ-これ以上yieldステートメントはありません!コードを読んで理解する。
  5. 関数を元の定義と比較します。

このトリックは、関数の背後にあるロジックのアイデアを与えるかもしれませんが、実際にyieldで起こることは、リストベースのアプローチで起こることとは大きく異なります。多くの場合、yieldアプローチはメモリ効率が非常に高く、高速です。他の場合では、元の関数が正常に機能していても、このトリックにより無限ループに陥ります。続きを読んで詳細をご覧ください...

イテラブル、イテレータ、ジェネレータを混同しないでください

まず、イテレータプロトコル-を書くとき

for x in mylist:
    ...loop body...

Pythonは次の2つの手順を実行します。

  1. mylistの反復子を取得します。

    iter(mylist)を呼び出す->これは、next()メソッド(またはPython 3の__next__())を持つオブジェクトを返します。

    [これは、ほとんどの人があなたに伝えることを忘れるステップです]

  2. イテレータを使用してアイテムをループします。

    ステップ1から返されたイテレーターでnext()メソッドを呼び出し続けます。next()からの戻り値がxに割り当てられ、ループ本体が実行されます。例外StopIterationnext()内から発生した場合、反復子に値がなくなったことを意味し、ループは終了します。

真実は、Pythonは、オブジェクトのコンテンツをループオーバーしたいときはいつでも、上記の2つのステップを実行します。 forループですが、otherlist.extend(mylist)otherlistはPythonリスト)のようなコードにすることもできます。

ここで、mylistiterableです。これは、反復プロトコルを実装しているためです。ユーザー定義のクラスでは、__iter__()メソッドを実装して、クラスのインスタンスを反復可能にすることができます。このメソッドはiteratorを返す必要があります。イテレータは、next()メソッドを持つオブジェクトです。 __iter__()next()の両方を同じクラスに実装し、__iter__()selfを返すようにすることができます。これは単純な場合には機能しますが、2つのイテレーターが同じオブジェクトを同時にループさせたい場合には機能しません。

これがイテレータプロトコルです。多くのオブジェクトがこのプロトコルを実装しています。

  1. 組み込みのリスト、辞書、タプル、セット、ファイル。
  2. __iter__()を実装するユーザー定義のクラス。
  3. ジェネレーター。

forループは、それがどの種類のオブジェクトを扱っているかを知らないことに注意してください-イテレータプロトコルに従うだけで、next()を呼び出すときに項目ごとに取得できます。組み込みリストは項目を1つずつ返し、辞書はkeysを1つずつ返し、ファイルは行を返します1つずつ、など。そして、ジェネレーターは戻ります... yieldが入る場所です:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yieldステートメントの代わりに、f123()に3つのreturnステートメントがある場合、最初のステートメントのみが実行され、関数は終了します。ただし、f123()は通常の関数ではありません。 f123()が呼び出されると、は返されませんyieldステートメントの値を返しません!ジェネレーターオブジェクトを返します。また、関数は実際には終了せず、中断状態になります。 forループがジェネレーターオブジェクトをループしようとすると、関数は、前回返されたyieldの直後の次の行で中断状態から再開し、この場合、次のコード行を実行します。 yieldステートメント、およびそれを次のアイテムとして返します。これは、関数が終了するまで発生し、その時点でジェネレーターがStopIterationを発生させ、ループが終了します。

そのため、ジェネレーターオブジェクトはアダプターのようなものです-一端、__iter__()およびnext()メソッドを公開してforループを維持するイテレータープロトコルを示します。ただし、もう一方の端では、次の値を取得するのに十分なだけ関数を実行し、サスペンドモードに戻します。

ジェネレーターを使用する理由

通常、ジェネレーターを使用せずに同じロジックを実装するコードを作成できます。 1つのオプションは、前述の一時リスト「トリック」を使用することです。それは、すべての場合に機能するわけではありません。無限ループがある場合、またはリストが非常に長い場合にメモリを非効率的に使用する可能性があります。もう1つの方法は、インスタンスメンバーの状態を保持し、next()(またはPythonの__next__()で次の論理ステップを実行する新しい反復可能クラスSomethingIterを実装することです。 ) 方法。ロジックによっては、next()メソッド内のコードが非常に複雑になり、バグが発生しやすくなる場合があります。ここで、ジェネレータはクリーンで簡単なソリューションを提供します。

1845
user28409

このように考えてください。

イテレータは、next()メソッドを持つオブジェクトの単なる空想的な用語です。そのため、降伏関数は次のようになります。

元のバージョン:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

これは基本的にPythonインタプリタが上記のコードを使ってすることです:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

舞台裏で何が起こっているのかについてのより多くの洞察力のために、forループはこれに書き直されることができます:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

それはもっと理にかなっているのでしょうか、それともあなたをもっと混乱させるのでしょうか。 :)

これはの説明のための単純化しすぎであることに注意してください。 :)

445
Jason Baker

yieldキーワードは、2つの単純な事実にまとめられています。

  1. コンパイラが関数内でyieldキーワード 任意の場所 を検出した場合、その関数はreturnステートメントを介して返されなくなります。 代わりに、それは即時lazy "pending list" objectを返します。
  2. ジェネレータは繰り返し可能です。 iterable とは何ですか?それはlistsetrangeあるいはdict-viewのようなもので、 特定の順序で各要素にアクセスするための組み込みのプロトコル です。

一言で言えば、ジェネレータは、怠惰で、増分保留のリスト、およびyieldステートメントを使用すると、リストの値をプログラムすることができます。ジェネレータは、インクリメンタルに吐き出す必要があります。

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

PythonのmakeRangeのような関数rangeを定義しましょう。 makeRange(n)を呼び出すとジェネレータが返されます。

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

ジェネレータに保留中の値をすぐに返させるには、それをlist()に渡します(繰り返し可能にできるのと同じように)。

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

例を「リストを返すだけ」と比較する

上記の例は単に追加して返すリストを作成することと考えることができます。

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

ただし、大きな違いが1つあります。最後のセクションを見てください。


ジェネレータの使い方

イテラブルはリスト内包表記の最後の部分であり、すべての生成子はイタラブルであるため、それらはしばしば次のように使用されます。

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

ジェネレータの感覚を良くするために、itertoolsモジュールを試してみることができます(保証されている場合はchainではなくchain.from_iterableを必ず使用してください)。たとえば、itertools.count()のような無限に長い遅延リストを実装するためにジェネレータさえ使うかもしれません。独自のdef enumerate(iterable): Zip(count(), iterable)を実装することも、whileループ内でyieldキーワードを使用して実装することもできます。

注意してください: コルーチンの実装 や非決定論的プログラミング、その他の洗練されたものなど、ジェネレータは実際にはもっと多くのことに使用できます。ただし、ここで紹介する「遅延リスト」の観点は、最も一般的な用途です。


舞台裏

これが「Python反復プロトコル」のしくみです。つまり、list(makeRange(5))を実行したときに何が起こっているのでしょうか。これが私が以前に "怠惰な、増分リスト"として説明しているものです。

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

組み込み関数next()はオブジェクト.next()関数を呼び出すだけです。これは「反復プロトコル」の一部であり、すべての反復子で見られます。あなたは手動でnext()関数(そして繰り返しプロトコルの他の部分)を使って手の込んだものを実装することができますが、通常は読みやすさを犠牲にしてです。


細目

通常、ほとんどの人は次の区別を気にしないで、おそらくここで読むのをやめたいと思うでしょう。

Pythonでは、 iterable はリスト[1,2,3]のように "for-loopの概念を理解している"オブジェクトであり、 iterator[1,2,3].__iter__()のような要求されたforループの特定のインスタンスです。 A generator は(関数構文による)記述方法以外はすべてのイテレータとまったく同じです。

リストから反復子を要求すると、新しい反復子が作成されます。ただし、イテレータからイテレータを要求すると(これはめったに行われません)、それ自体がコピーされます。

このように、あなたがこのようなことをするのに失敗しているというありそうもない出来事において...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

...そして、ジェネレータは イテレータ であることを思い出してください。つまり、使い捨てです。再利用したい場合は、もう一度myRange(...)を呼び出す必要があります。結果を2回使用する必要がある場合は、結果をリストに変換して変数x = list(myRange(5))に格納してください。コピー可能な反復子Python PEP 標準の提案は延期されているので、絶対にジェネレーターを複製する必要がある人(例えば、ひどくハックなメタプログラミングをしている人)は itertools.tee を絶対に必要とします。

380
ninjagecko

yieldreturnと同じです - あなたが指示したものを(ジェネレータとして)返します。違いは、次にジェネレータを呼び出すときに、最後のyield文の呼び出しから実行が開始されることです。 returnとは異なり、 イールドが発生してもスタックフレームはクリーンアップされませんが、呼び出し元に制御が戻されるため、次に関数が呼び出されたときにその状態が再開されます。

あなたのコードの場合、関数get_child_candidatesはイテレータのように振る舞うので、リストを拡張するとき、それは一度に1つの要素を新しいリストに追加します。

list.extendは、使い果たされるまでイテレータを呼び出します。あなたが投稿したコードサンプルの場合、Tupleを返してそれをリストに追加するほうがずっと明確です。

260
Douglas Mayle

もう1つ注意が必要なことがあります。yieldを与える関数は実際には終了する必要がないということです。私はこのようなコードを書きました:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

それから私はこのような他のコードでそれを使うことができます:

for f in fib():
    if some_condition: break
    coolfuncs(f);

それは本当にいくつかの問題を単純化するのを助け、そしていくつかの事をより簡単にします。 

199
Claudiu

TL; DR

これの代わりに:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

これを行う:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

ゼロからリストを作成していることに気づいたときは、代わりにyieldの各ピースを代わりに。

これは私の最初の「あは」の瞬間でした。


yieldsugary の言い方です

一連のものを構築する

同じ動作:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

異なる動作:

収量はsingle-passです:1回だけ反復できます。関数にyieldが含まれる場合、 generator function と呼びます。そして、 iterator はそれが返すものです。これらの用語は明らかになっています。コンテナの利便性は失われますが、必要に応じて計算され、任意の長さのシリーズのパワーが得られます。

収量はlazyであり、計算を先送りします。 yieldを含む関数は、呼び出しても実際にはまったく実行されません。中断した場所を記憶する iterator object を返します。イテレータでnext()を呼び出すたびに(これはforループで発生します)、次のyieldまで実行を数インチ進めます。 returnはStopIterationを発生させ、シリーズを終了します(これはforループの自然な終了です)。

収量は汎用性です。データをすべて一緒に保存する必要はなく、一度に1つずつ利用可能にすることができます。無限にすることができます。

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

複数のパスが必要で、シリーズが長すぎない場合は、list()を呼び出します:

>>> list(square_yield(4))
[0, 1, 4, 9]

両方の意味 が適用されるため、Word yieldの素晴らしい選択:

yield—生産または提供(農業など)

...シリーズの次のデータを提供します。

yield—道を譲るか放棄するか(政治権力のように)

...イテレータが進むまでCPUの実行を放棄します。

184
Bob Stein

最小限の実用的な例を好む人のために、このインタラクティブな Python sessionについて黙想してください。

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
171
Daniel

ジェネレーターを返しています。私はPythonに特に精通しているわけではありませんが、それらに精通していれば C#のイテレーターブロック と同じようなものだと思います。

重要なアイデアは、コンパイラ/インタープリター/何でもトリックを行うことです。そのため、呼び出し元に関する限り、next()を呼び出し続けることができ、値を返し続けます-ジェネレーターメソッドが一時停止されました。明らかに、メソッドを実際に「一時停止」することはできないので、コンパイラは、現在の場所とローカル変数などがどのように見えるかを覚えておくためのステートマシンを構築します。これは、イテレータを自分で記述するよりもはるかに簡単です。

161
Jon Skeet

収量はあなたにジェネレータを与えます。 

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

ご覧のとおり、最初の場合、fooはリスト全体を一度にメモリに保持します。 5つの要素を持つリストにとって大したことではありませんが、500万のリストが必要な場合はどうしますか。これは巨大なメモリを消費するだけでなく、関数が呼び出される時点で構築するのにも多くの時間がかかります。後者の場合、barは単にジェネレータを提供します。ジェネレータはイテラブルです - つまりforループなどでそれを使うことができるということですが、それぞれの値は一度しかアクセスできません。すべての値も同時にメモリに格納されません。ジェネレータオブジェクトは、前回呼び出したときのループの位置を「記憶」しています。つまり、500億までの反復可能変数を使用している場合は、500億までカウントする必要はありません一度に数えて500億の数字を保存します。繰り返しますが、これはかなり人為的な例です。あなたが本当に500億まで数えたいのなら、おそらくあなたはitertoolsを使うでしょう。 :)

これはジェネレータの最も単純なユースケースです。あなたが言ったように、それは効率的な順列を書くために使うことができ、ある種のスタック変数を使う代わりにyieldを使って呼び出しスタックを通して物事を押し上げることができます。ジェネレータは特殊なツリートラバースやその他のあらゆる方法にも使用できます。

148
RBansal

発電機の使い方を説明している多くの素晴らしい答えの中には、私がまだ与えられていないと感じる答えがあります。これがプログラミング言語理論の答えです。

Pythonのyieldステートメントはジェネレータを返します。 Pythonのジェネレータは、を返す関数です。 続き (そして特に一種のコルーチンですが、継続は何が起こっているのかを理解するためのより一般的なメカニズムを表します)。

プログラミング言語理論における継続は、はるかに基本的な種類の計算ですが、それらを使用することは非常に困難であり、実装も非常に難しいため、使用されることはあまりありません。しかし、継続とは何かという考えは簡単です。それはまだ終わっていない計算の状態です。この状態では、変数の現在の値、まだ実行されていない操作などが保存されます。その後、プログラムの後の時点で、プログラムの変数がその状態にリセットされ、保存された操作が実行されるように、継続を呼び出すことができます。

このより一般的な形式の継続は、2つの方法で実装できます。 call/ccの方法では、プログラムのスタックは文字通り保存され、そして継続が呼び出されるとスタックが復元されます。

継続渡しスタイル(CPS)では、継続はプログラマが明示的に管理してサブルーチンに渡す通常の関数(関数が第1クラスである言語のみ)です。このスタイルでは、プログラム状態はスタック上のどこかに存在する変数ではなく、クロージャー(およびその中にエンコードされることがある変数)によって表されます。制御フローを管理する関数は引数として継続を受け入れ(CPSのいくつかのバリエーションでは、関数は複数の継続を受け入れることができます)、単にそれらを呼び出して後で返すことによってそれらを呼び出すことによって制御フローを操作します。継続渡しスタイルの非常に単純な例は次のとおりです。

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

この(非常に単純な)例では、プログラマーは実際にファイルを継続に書き込む操作(多くの詳細を含む非常に複雑な操作になる可能性があります)を保存してから、その継続を渡します(つまり、最初の(クラスクロージャ)、他の演算子に追加処理を行い、必要に応じてそれを呼び出します。 (この設計パターンは実際のGUIプログラミングでよく使用されています。コードの行数を節約するため、またはもっと重要なことには、GUIイベントがトリガーされた後の制御フローを管理するためです。)

この記事の残りの部分では、一般性を失うことなく、継続をCPSとして概念化します。なぜなら、それは理解して読むのがはるかに容易な地獄だからです。


それでは、Pythonのジェネレータについて話しましょう。ジェネレータは継続の特定のサブタイプです。 継続は一般的に計算(つまりプログラムの呼び出しスタック)の状態を保存することができますが、 ジェネレータは_を超えた繰り返しの状態を保存することしかできません。反復子。ただし、この定義はジェネレータの特定のユースケースでは少し誤解を招きます。例えば:

def f():
  while True:
    yield 4

これは明らかにその動作が明確に定義された妥当なイテラブルです - ジェネレータがそれをイタレートするたびに4を返します(そしていつまでもそうなります)。しかし、イテレータを考えるときに頭に浮かぶのは、おそらくプロトタイプ型のイテラブルではありません(つまりfor x in collection: do_something(x))。この例はジェネレータの力を説明します:何かがイテレータであるならば、ジェネレータはその繰り返しの状態を保存することができます。

繰り返すと、継続はプログラムのスタックの状態を保存でき、ジェネレータは繰り返しの状態を保存できる。つまり、継続はジェネレータよりもはるかに強力ですが、ジェネレータははるかに簡単です。言語設計者にとっては実装が簡単で、プログラマーにとっては使いやすいでしょう(やや時間がある場合は、読んで理解してください このページの継続と/ ccの呼び出し )。

しかし、継続渡しスタイルの単純で具体的なケースとして、ジェネレータを簡単に実装(および概念化)することができます。

yieldが呼び出されるたびに、継続を返すように関数に指示します。関数が再度呼び出されると、中断したところから開始されます。したがって、疑似疑似コード(つまり、疑似コードではなくコードではない)では、ジェネレータのnextメソッドは基本的に次のようになります。

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

ここでyieldキーワードは実際の生成関数のための実際には構文糖です。基本的には次のようなものです。

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

これは単なる疑似コードであり、Pythonでのジェネレータの実際の実装はより複雑です。しかし、何が起こっているのかを理解するための演習として、yieldキーワードを使用せずに継続渡しスタイルを使用してジェネレータオブジェクトを実装するようにしてください。

134
aestrivex

これはわかりやすい言語での例です。私は、高レベルの人間の概念と低レベルのPythonの概念との対応関係を提供します。

数字のシーケンスを操作したいのですが、そのシーケンスを作成することに煩わされたくはありません。自分がしたい操作だけに集中したいのです。それで、私は以下をします:

  • 私はあなたに電話して、私は特定の方法で作り出される一連の数が欲しいとあなたに言います、そして私はあなたにそのアルゴリズムが何であるかを知らせます。 
    このステップはジェネレータ関数、すなわちdefを含む関数をyieldにすることに対応する。
  • しばらくして、私はあなたに言います、「さて、私に一連の数字を言う準備をしなさい」。 
    このステップは、ジェネレータオブジェクトを返すジェネレータ関数を呼び出すことに対応しています。 あなたはまだ私に数を伝えていないことに注意してください。紙と鉛筆をつかむだけです。
  • 私はあなたに「次の番号を教えて」と頼みます、そしてあなたは私に最初の番号を教えます。その後、あなたは私が次の番号を尋ねるのを待ちます。あなたがどこにいたか、あなたがすでに言った数字、そして次の数字は何かを覚えておくのはあなたの仕事です。詳細は気にしません。 
    このステップはジェネレータオブジェクトの.next()の呼び出しに対応しています。
  • …前のステップを…まで繰り返す
  • 結局、あなたは終わりを迎えるかもしれません。あなたは私に数を言わないでください。 「馬を抱えて!やり終わった!これ以上の数字はない!」 
    このステップは、ジェネレータオブジェクトがその仕事を終了し、StopIteration例外を発生させることに対応します。 ジェネレータ関数は例外を発生させる必要はありません。関数が終了するかreturnを発行すると自動的に発生します。

これがジェネレータが行うことです(yieldを含む関数)。それは実行を開始し、それがyieldを実行するときはいつでも一時停止し、そして.next()値を要求されるとそれはそれが最後であった点から継続します。 Pythonのイテレータプロトコルを使用した設計により、値を連続して要求する方法を説明しています。

イテレータプロトコルの最も有名なユーザーは、Pythonのforコマンドです。だから、あなたがするたびに:

for item in sequence:

sequenceがリスト、文字列、辞書、ジェネレータのいずれであってもかまいません object 上記のように。結果も同じです。シーケンスから項目を1つずつ読み取っていきます。

defキーワードを含む関数をyieldiningすることがジェネレータを作成する唯一の方法ではないことに注意してください。それを作成する最も簡単な方法です。

より正確な情報については、Pythonのドキュメントの イテレータ型yieldステートメント 、および ジェネレータ について読んでください。

120
tzot

ジェネレータを作成するのにyieldを使用する理由はたくさんありますが、yieldにはもっと用途があります。コルーチンを作成するのは非常に簡単です。これにより、2つのコードブロック間で情報を渡すことができます。 yieldを使ってジェネレータを作成することに関してすでに与えられたすばらしい例は何も繰り返しません。

以下のコードでyieldが何をするのかを理解するのを助けるために、あなたはあなたの指を使ってyieldを持っているコードを通してサイクルをたどることができます。指がyieldに当たるたびに、nextまたはsendが入力されるのを待つ必要があります。 nextが呼び出されると、yieldに達するまでコードをトレースします。yieldの右側にあるコードが評価され、呼び出し元に返されます。 nextが再度呼び出されると、コードを介して別のループが実行されます。しかし、コルーチンでは、yieldsend…と一緒に使用して、呼び出し側の から に降伏関数を送ることもできます。 sendが与えられた場合、yieldは送信された値を受け取り、それを左側に吐き出します。その後、yieldに再度ヒットするまでコードのトレースが進みます(nextが呼び出された場合と同様に最後に値が返されます)。

例えば:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
105
Mike McKerns

(Python 3.3以降)yieldの使い方と意味があります。

yield from <expr>

FromPEP 380 - サブジェネレーターに委任するための構文

ある演算子がその操作の一部を別の生成子に委任するための構文が提案されています。これにより、「yield」を含むコードのセクションを因数分解して別のジェネレータに配置することができます。さらに、サブジェネレータは値を返すことができ、その値はデリゲートジェネレータに利用可能になります。

新しい構文はまた、あるジェネレータが別のジェネレータによって生成された値を再生成するときに最適化のためのいくつかの機会を切り開きます。

さらに これ で紹介します(Python 3.5以降):

async def new_coroutine(data):
   ...
   await blocking_action()

コルーチンが通常のジェネレータと混同されないようにするため(今日ではyieldが両方で使用されています)。

97

すべての素晴らしい答えですが、初心者には少し難しいです。

returnステートメントを学習したと思います。

類推として、returnyieldは双子です。 returnは 'return and stop'を意味し、 'yield`は' return、but continue 'を意味します

  1. returnでnum_listを取得してください。
def num_list(n):
    for i in range(n):
        return i

それを実行します:

In [5]: num_list(3)
Out[5]: 0

参照してください、あなたはそれらのリストではなく単一の数字だけを取得します。 returnは、あなたが幸せに勝つことを決して許さず、一度実装して終了するだけです。

  1. yieldが来ます

returnyieldに置き換えます。

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

今、あなたはすべての数字を得るために勝ちます。

一度実行して停止するreturnと比較すると、yieldは計画した時間を実行します。 returnreturn one of themとして、yieldreturn all of themとして解釈できます。これはiterableと呼ばれます。

  1. yieldステートメントをreturnで書き直すことができるもう1つのステップ
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

yieldの中心です。

リストreturn出力とオブジェクトyield出力の違いは次のとおりです。

リストオブジェクトからは常に[0、1、2]を取得しますが、「オブジェクトyield出力」から取得できるのは一度だけです。そのため、Out[11]: <generator object num_list at 0x10327c990>に表示される新しい名前generatorオブジェクトを持ちます。

結論として、それを理解するための隠phorとして:

  • returnyieldは双子です
  • listgeneratorは双子です
95
Algebra

Pythonがあたかも構文糖を提供していないかのように実際にジェネレータを実装する方法のいくつかのPythonの例は以下の通りです。

Pythonジェネレータとして:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

ジェネレータの代わりに字句クロージャを使う

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

ジェネレータの代わりにオブジェクトクロージャを使用する ClosuresAndObjectsAreEquivalent

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
83
Dustin Getz

私は「ジェネレータの簡単な説明についてはBeazleyの 'Python:Essential Reference'の19ページを読む」を投稿するつもりでしたが、他の多くの人がすでに良い説明を投稿しています。

また、yieldはジェネレータ関数での使用の双対としてコルーチンで使用できます。コードスニペットと同じ用途ではありませんが、(yield)は関数内の式として使用できます。呼び出し元がsend()メソッドを使用してメソッドに値を送信すると、コルーチンは次の(yield)ステートメントが見つかるまで実行されます。

ジェネレータとコルーチンは、データフロータイプのアプリケーションを設定するための素晴らしい方法です。関数内でのyieldステートメントの他の使い方について知ることは価値があると思いました。

81
johnzachary

プログラミングの観点からは、反復子は thunks として実装されています。

サンク(匿名関数とも呼ばれる)として、同時実行などのためにイテレータ、ジェネレータ、およびスレッドプールを実装するには、ディスパッチャを持つクロージャオブジェクトに送信されるメッセージを使用し、ディスパッチャは「メッセージ」に答えます。

http://en.wikipedia.org/wiki/Message_passing

" next "はクロージャに送信されるメッセージで、 " iter "呼び出しによって作成されます。

この計算を実装する方法はたくさんあります。私は突然変異を使いましたが、現在の値と次の野手を返すことによって、突然変異なしでそれをするのは簡単です。

これはR6RSの構造を使ったデモですが、意味はPythonのものと全く同じです。これは同じ計算モデルであり、Pythonで書き直すために必要なのは構文の変更だけです。

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
77
alinsoar

これは簡単な例です:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

出力:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

私はPython開発者ではありませんが、yieldはプログラムフローの位置を保持し、次のループは "yield"位置から開始します。それはその位置で待っていて、その直前に値を外部に返しているように見えます、そして次回は働き続けます。

それは面白くて素敵な能力のようです:D

70
Engin OZTURK

これはyieldがすることの精神的なイメージです。

私はスレッドがスタックを持っていると考えるのが好きです(たとえそのように実装されていなくても)。

通常の関数が呼び出されると、ローカル変数をスタックに置き、何らかの計算をしてからスタックをクリアして戻ります。その局所変数の値は二度と見られない。

yield関数では、コードの実行が開始されると(つまり、関数が呼び出された後にジェネレータオブジェクトが返され、そのnext()メソッドが呼び出されると)、同様にローカル変数がスタックに置かれ、しばらくの間計算されます。しかし、それがyieldステートメントにぶつかると、スタックのその部分をクリアして戻る前に、ローカル変数のスナップショットを取り、それらをジェネレータオブジェクトに格納します。また、現在コードに含まれている場所(つまり特定のyieldステートメント)も書き留めます。

それで、ジェネレータがハングしているのは一種の凍結された関数です。

続いてnext()が呼び出されると、関数の所持品をスタックに取り出して再アニメートします。この関数は、中断したところから計算を続けます。これは、冷蔵室で永遠に過ごしたばかりの事実には関係ありません。

次の例を比較してください。

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

2番目の関数を呼び出すと、最初の関数とはまったく異なる動作をします。 yieldステートメントは到達不能かもしれませんが、それがどこかに存在する場合、それは私たちが扱っているものの性質を変えます。

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

yielderFunction()を呼び出してもコードは実行されませんが、コードからジェネレータが作成されます。 (読みやすくするために、そのようなものに接頭部yielderを付けることをお勧めします。)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_codeおよびgi_frameフィールドは、フリーズ状態が保存される場所です。それらをdir(..)で調べてみると、上記の精神的モデルが信頼できることを確認できます。

58
Evgeni Sergeev

すべての答えが示唆するように、yieldはシーケンスジェネレータを作成するために使用されます。動的にシーケンスを生成するために使用されます。たとえば、ネットワーク上でファイルを1行ずつ読み取るときは、次のようにyield関数を使用できます。

def getNextLines():
   while con.isOpen():
       yield con.read()

次のようにあなたのコードでそれを使用することができます:

for line in getNextLines():
    doSomeThing(line)

実行制御転送gotcha

Yieldが実行されると、実行制御はgetNextLines()からforループに渡されます。したがって、getNextLines()が呼び出されるたびに、最後に一時停止されたところから実行が開始されます。

つまり、以下のコードの関数

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

印刷します

"first time"
"second time"
"third time"
"Now some useful value 12"

(私の下の答えはPythonジェネレータを使うという観点からのみ話すもので、 ジェネレータメカニズムの基本的な実装 、これにはスタックとヒープの操作のトリックが含まれます)ではなく)

Python関数でyieldの代わりにreturnが使用されると、その関数はgenerator functionと呼ばれる特別なものに変わります。その関数はgenerator型のオブジェクトを返します。yieldキーワードはpythonコンパイラにそのような関数を特別に扱うよう通知するフラグです。 通常の関数は、そこから何らかの値が返されると終了します。しかし、コンパイラの助けを借りて、ジェネレータ関数 は再開可能として と考えることができます。つまり、実行コンテキストが復元され、最後の実行から実行が続行されます。明示的にreturnを呼び出すまで、これはStopIteration例外(これもイテレータプロトコルの一部です)を発生させるか、関数の終わりに到達します。私はgeneratorについてたくさんの参照を見つけましたが、これは onefunctional programming perspectiveからのものが最もダイジェスト可能です)。

(今私はgeneratorの背後にある論理的根拠と、私自身の理解に基づくiteratorについてお話したいと思います。これがイテレータとジェネレータの本質的な動機を理解するのに役立つことを願います。 C#など、他の言語でも表示されます。)

私が理解しているように、大量のデータを処理したいときは、通常、まずどこかにデータを保管してから、1つずつ処理します。しかしこの素朴なアプローチは問題が多いです。データ量が膨大な場合は、事前に全体を保存するのは費用がかかります。data自体を直接格納するのではなく、何らかの種類のmetadataを間接的に格納しないでください。つまり、the logic how the data is computedです。 

そのようなメタデータをラップする2つのアプローチがあります。

  1. OOアプローチでは、メタデータas a classをラップします。これは、イテレータプロトコルを実装した、いわゆるiteratorです(つまり、__next__()__iter__()メソッド)。これはまたよく見られる イテレータデザインパターン です。
  2. 機能的なアプローチでは、メタデータas a functionをラップします。これはいわゆるgenerator functionです。しかし内部的には、返されるgenerator objectはまだIS-A iteratorであり、イテレータプロトコルも実装しているためです。

どちらの方法でも、イテレータ、つまりあなたが望むデータをあなたに与えることができる何らかのオブジェクトが作成されます。 OOアプローチは少し複雑かもしれません。とにかく、どちらを使うかはあなた次第です。

43
smwikipedia

収量はオブジェクトです

関数内のreturnは単一の値を返します。

関数が巨大な値の集合を返すようにしたい場合は yieldを使用してください。

さらに重要なことに、yield barrier です。

cUDA言語の障壁のように、それはが完成するまで支配権を譲渡しません。

つまり、最初からyieldに達するまで、関数内のコードを実行します。それから、ループの最初の値を返します。

それから、他のすべての呼び出しは、関数に書いたループをもう一度実行し、返す値がなくなるまで次の値を返します。

43
Kaleem Ullah

まとめると、yieldステートメントはあなたの関数をファクトリに変換し、ファクトリは元の関数の本体を包むgeneratorと呼ばれる特別なオブジェクトを生成します。 generatorが反復されると、次のyieldに達するまで関数を実行してから実行を中断し、yieldに渡された値に評価されます。実行パスが関数を出るまで、各反復でこのプロセスを繰り返します。例えば、

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

単に出力する

one
two
three

シーケンスを計算するループでジェネレータを使用することで得られるパワーは、次の計算結果を得るために毎回ループを停止して実行します。このようにして、リストをその場で計算します。特に大きな計算用に保存

反復可能な範囲の数値を生成する独自のrange関数を作成したいとします。

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

そしてそれをこのように使う。

for i in myRangeNaive(10):
    print i

しかしこれは非効率的です

  • あなたは一度だけ使用する配列を作成します(これはメモリを浪費します)
  • このコードは実際にはその配列を2回ループします。 :(

幸いなことに、Guidoと彼のチームはジェネレータを開発するのに十分寛大でした。

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

繰り返しのたびに、next()と呼ばれるジェネレータ上の関数は、それが停止して値を「譲渡」する「yield」文に達するか、関数の最後に達するまで関数を実行します。この場合、最初の呼び出しでnext()はyield文とyield 'n'まで実行され、次の呼び出しではincrement文を実行し、 'while'に戻って評価し、trueの場合は停止します。そして再び 'n'を返すと、while条件がfalseを返してジェネレータが関数の終わりにジャンプするまで、そのまま続けます。

42
redbandit

多くの人がreturnではなくyieldを使用していますが、場合によってはyieldのほうが効率的で作業が簡単になります。

これはyieldが間違いなく最適な例です。

return(関数内)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

yield(関数内)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

呼び出し関数

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

両方の関数は同じことをしますが、yieldは5行ではなく3行を使用し、心配する変数が1つ少なくなります。

これはコードの結果です。

Output

ご覧のとおり、両方の機能が同じことをしています。唯一の違いは、return_dates()がリストを与え、そしてyield_dates()がジェネレータを与えるということです。

実生活の例としては、ファイルを1行ずつ読み取る、または単にジェネレータを作成する場合などがあります。

40
Tom Fuller

yieldは関数のreturn要素のようなものです。違いは、yield要素が関数をジェネレータに変えることです。何かが「生成される」まで、ジェネレータは関数のように振舞います。ジェネレータは次に呼び出されるまで停止し、開始時とまったく同じ場所から続行します。 list(generator())を呼び出すことで、すべての「得られた」値のシーケンスを1つにまとめることができます。

35
Theoremiser

yieldキーワードは単に返される結果を収集します。 return +=のようにyieldを考える

35

これが、フィボナッチ数列を計算するための単純なyieldベースのアプローチです。

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

これをREPLに入力して呼び出してみると、不可解な結果が得られます。

>>> fib()
<generator object fib at 0x7fa38394e3b8>

これは、yieldの存在が、 generator 、つまり要求に応じて値を生成するオブジェクトを作成することをPythonに通知したためです。

それで、どのようにこれらの値を生成しますか?これは組み込み関数nextを使って直接行うことも、値を消費する構造体にそれを間接的に渡すことによっても行うことができます。 

組み込みのnext()関数を使用して、直接.next/__next__を呼び出し、ジェネレータに値を生成させる。

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

間接的に、fibforループ、listイニシャライザ、Tupleイニシャライザ、または値を生成/生成するオブジェクトを必要とするその他のものに提供すると、それ以上値を生成できなくなるまでジェネレータを「消費」しますそしてそれは返す:

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

同様に、Tupleイニシャライザを使って: 

>>> Tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

ジェネレータは、それが怠惰であるという意味で関数とは異なります。これは、そのローカルな状態を維持し、必要なときにいつでも再開できるようにすることで実現します。 

最初にfibを呼び出して呼び出すときは、次のようにします。

f = fib()

Pythonは関数をコンパイルし、yieldキーワードに出会い、単純にジェネレータオブジェクトを返します。あまり役に立ちません。 

その後、直接または間接的に最初の値を生成するように要求すると、yieldに遭遇するまで、検出したすべてのステートメントを実行し、次にyieldに指定した値を返して休止します。これをよりよく示す例として、いくつかのprint呼び出しを使用しましょう(Python 2の場合はprint "text"に置き換えます)。

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

それでは、REPLを入力してください。

>>> gen = yielder("Hello, yield!")

値を生成するためのコマンドを待っているジェネレータオブジェクトがあります。 nextを使用して、何が表示されるのかを確認してください。

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

引用符で囲まれていない結果は印刷されたものです。引用された結果はyieldから返されるものです。もう一度nextを呼び出します。

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

ジェネレータはyield valueで一時停止したことを記憶し、そこから再開します。次のメッセージが表示され、一時停止するyieldステートメントの検索が再度実行されました(whileループのため)。

簡単に説明されている簡単な例:yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

出力は以下のとおりです。 

1 2 1 2 1 2 1 2
29
Gavriel Cohen

さらに別のTL、DR

リストのイテレータnext()はリストの次の要素を返します

Iterator generatornext()はその場で次の要素を計算します(コードを実行します)

nextを呼び出すことで、外部から手動で制御フローを実行する方法としてyield/generatorを見ることができますが、フローは複雑になります。

:ジェネレータはNOT通常の関数です。ローカル変数(スタック)のように以前の状態を覚えています。詳しい説明は他の答えや記事を見てください。ジェネレータは1回反復のみです。 yieldがなくてもできますが、それほどいいものではないので、「とてもいい」言語の糖と見なすことができます。

28

yield はreturnに似ています。違いは: 

yield 関数を反復可能にします(次の例ではprimes(n = 1)関数が反復可能になります)。
これが本質的に意味するのは、次にその関数が呼び出されたときに、それが離れた場所(yield expressionの行の後)から続行されることです。

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

上記の例でisprime(n)が真ならば素数を返します。次の繰り返しでは次の行から続く 

n += 1  
24
blueray

ここでの答えはすべて素晴らしいです。しかし、それらのうちの1つ(最も投票されたもの)だけが あなたのコードがどのように機能するのか - に関係しています 。他のものは一般に ジェネレータ 、そしてそれらがどのように働くかに関係しています。

だから私は発電機が何であるか、またはどのような利回りが何をするのか繰り返すつもりはない。これらは既存の優れた答えでカバーされていると思います。しかし、あなたと似たようなコードを理解しようとして数時間を費やした後、私はそれがどのように機能するかについてそれを分解します。

あなたのコードは二分木構造をたどります。この木を例にとりましょう:

    5
   / \
  3   6
 / \   \
1   4   8

そして、二分探索木探索のもう一つのより簡単な実装は:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

実行コードはTreeオブジェクトにあります。これは__iter__を次のように実装します。

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

while candidatesステートメントはfor element in treeに置き換えることができます。 Pythonはこれをに翻訳します

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Node.__iter__関数はジェネレータなので、コード その中の は反復ごとに実行されます。そのため、実行は次のようになります。

  1. ルート要素が最初です。子が残っているかどうかを確認し、for反復します(最初の反復子オブジェクトはit1とします)。
  2. それは子を持っているのでforが実行されます。 for child in self.leftは、Nodeオブジェクトそのものであるself.leftから 新しいイテレータ を作成します(it2)
  3. 2と同じロジックで、新しいiteratorが作成されます(it3)
  4. 今、私たちは木の左端に達しました。 it3には残っている子がいないので続けます、そしてyield self.value
  5. 次のnext(it3)の呼び出しでは、StopIterationが発生し、正しい子がないため存在します(降伏することなく関数の最後まで到達します)。
  6. it1it2はまだアクティブです - それらは使い果たされず、next(it2)を呼び出すことは_を引き上げないで値をもたらすでしょうStopIteration
  7. it2コンテキストに戻り、yield childステートメントの直後に停止したところからnext(it2)を呼び出します。もう子が残っていないので、続けてself.valを返します。

ここでのキャッチは、すべての反復 がサブ反復子 を作成してツリーをトラバースし、現在の反復子の状態を保持することです。それが終わりに達するとそれはスタックを逆行し、値は正しい順序で返されます(最小の降伏値が最初になります)。

あなたのコード例は別のテクニックで似たようなことをしました:それはすべての子供のために 1要素のリスト を埋め、それから次の繰り返しでそれをポップして現在のオブジェクトで関数コードを実行します(したがってself).

これがこの伝説的なトピックに少し貢献したことを願っています。私はそれを理解するためにこのプロセスを描くために数時間を費やしました。

10
Chen A.

つまり、 yield の使用法は、 generator を返す点を除いて、キーワード return に似ています。
generator objectは、 once だけを横断します。

yield には2つの利点があります。 

  1. これらの値を2回読み取る必要はありません。 
  2. すべての子ノードをメモリに入れなくても取得できます。
8
123

Pythonでは、generators(特別なタイプのiterators)が一連の値を生成するために使用され、yieldキーワードはジェネレータ関数のreturnキーワードと同じです。 

yieldキーワードがするもう一つの魅力的なことはジェネレータ関数のstateを保存することです 。 

そのため、numberが生成されるたびにgeneratorを異なる値に設定できます。 

これがインスタンスです:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
7
ARGeo

産出

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
0
1
4

要するにを見ると、(実行後にループが停止するreturnとは異なり)オブジェクトまたは変数が送信された後でも、ループは停止せず機能し続けます。

6
Gavriel Cohen

例えはここでアイデアを把握するのを助けるかもしれません:

あなたが1日に何千もの電球を生成することができる驚くべき機械を作成したと想像してください。本機は、これらの電球を固有のシリアル番号の付いた箱に入れます。これらすべての電球を同時に保管するのに十分なスペースがありません(つまり、保管上の制限から機械の速度に追いつくことができません)。したがって、この電球をオンデマンドで生成するように調整します。

Pythonジェネレータはこの概念とそれほど違いはありません。

ボックスのためにユニークなシリアル番号を生成する関数xがあると想像してください。明らかに、あなたは関数によって生成された非常に多数のそのようなバーコードを持つことができます。より賢い、そしてスペース効率の良い選択肢は、それらのシリアル番号をオンデマンドで生成することです。

機械のコード:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

ご覧のとおり、次の固有のシリアル番号を毎回生成するための自己完結型の「関数」があります。この関数はジェネレータを返します!ご覧のとおり、新しいシリアル番号が必要になるたびに関数を呼び出すわけではありませんが、次のシリアル番号を取得するためにジェネレータを指定してnext()を使用しています。

出力:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
6
Rafael

yieldはpythonで使用できるジェネレータの一種です。

これは、Yieldが実際に何をしているのかを見るためのリンクです。 ジェネレータとYieldキーワード - Python Central(PC)

yieldreturnと同じように動作しますが、returnとは異なる方法でyieldを説明するリンクがあっても、理解できない場合もう一人はそんなによくない。 歩留まりスキルを向上させる - jeffknupp

簡単に言うと、 'yield'は値を 'return'するのと似ていますが、Generatorで機能します。

1
user3701435

単純yieldでは、値ではなくジェネレータオブジェクトを返します。 

以下の簡単な例が役立ちます!

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

上記のコードは0、1、2を返します。

あるいは短い

for val in sim_generator():
    print(val)

0、1、2を返す

お役に立てれば

1
Vivek Ananthan

簡単なジェネレータ関数

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

yield文は関数がすべての状態を保存して一時停止し、その後の呼び出しでそこから続行します。

https://www.programiz.com/python-programming/generator

0

yieldは何かを生み出します。誰かがあなたに5カップケーキを作るように頼むようなものです。あなたが少なくとも1つのカップケーキを使い終わったら、あなたが他のケーキを作る間、それを食べるように彼らに与えることができます。

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

ここではfactoryはgeneratorと呼ばれ、あなたがケーキを作ります。 make_functionを呼び出すと、その関数を実行する代わりにジェネレータを取得します。 yieldキーワードが関数内に存在する場合、それがジェネレータになるからです。

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

彼らはすべてのケーキを消費しましたが、彼らは再び1つを求めます。

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

そして、彼らはもっと尋ねるのをやめるように言われています。それで、あなたが発電機を消費したら、あなたはそれで終わりです。もっとケーキが欲しいなら、もう一度make_cakeを呼ぶ必要があります。カップケーキを注文するようなものです。

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

上記のようなジェネレータを使ってforループを使うこともできます。

もう1つの例:あなたがそれを求めるときはいつでもあなたがランダムなパスワードが欲しいと言うことができます。

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

ここでrpgはジェネレータです。これは無限のランダムパスワードを生成することができます。そのため、要素数が有限のlistとは異なり、シーケンスの長さがわからない場合にもジェネレータが有用であると言えます。

0
thavan