「yield」キーワードは何をしますか?


10195

yieldPython でのキーワードの用途とは何ですか?

たとえば、私はこのコードを理解しようとしています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.このコードは、メトリック空間用の優れたPythonライブラリを作成したJochen Schulz(jrschulz)によって書かれました。これは完全なソースへのリンクです:Module mspace

回答:


14647

yieldが機能するかを理解するには、ジェネレータとは何かを理解する必要があります。ジェネレータを理解する前に、イテラブルを理解する必要があります。

イテラブル

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

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

mylistある反復可能。リスト内包表記を使用すると、リストが作成され、反復可能になります。

>>> 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

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

産出

yieldreturn関数がジェネレータを返すことを除いて、のように使用されるキーワードです。

>>> 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 the 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 the 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 candidate's 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の場合は、print(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については、この記事で詳しく説明しています


355
yieldこの答えが示唆するほど魔法ではありません。yieldどこかにステートメントを含む関数を呼び出すと、ジェネレーターオブジェクトが取得されますが、コードは実行されません。その後、ジェネレータからオブジェクトを抽出するたびに、Pythonはyieldステートメント内になるまで関数内のコードを実行し、その後、一時停止してオブジェクトを配信します。別のオブジェクトを抽出すると、Pythonはの直後から再開され、別のオブジェクトyieldに到達するまで続きますyield(多くの場合、同じオブジェクトですが、1回繰り返します)。これは、関数が最後まで実行されるまで続き、その時点で、ジェネレータは使い果たされたと見なされます。
Matthias Fripp

30
「これらのイテラブルは便利です...しかし、すべての値をメモリに格納しますが、これが常に必要なものであるとは限りません」とは、間違っているか混乱しています。反復可能にITER()を呼び出すときに、イテレータ、イテレータは常にの実装に依存し、メモリにその値を格納する必要はありません反復可能なリターンITERの方法は、それはまた、オンデマンドでシーケンス内の値を生成することができます。
picmate涅

この素晴らしい答えに、なぜの代わりに使用した以外はまったく同じである()[]か、具体的には何であるかを追加するとよいでしょう()(具体的には、タプルと混同される可能性があります)。
WoJ

私は間違っているかもしれませんが、ジェネレータはイテレータではありません。「呼び出されたジェネレータ」はイテレータです。
aderchox

2007

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

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

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

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

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

まず、イテレータプロトコル -あなたが書くとき

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

Pythonは次の2つのステップを実行します。

  1. のイテレータを取得しますmylist

    呼び出しiter(mylist)-> next()メソッドを使用してオブジェクトを返します(または__next__()Python 3の場合)。

    [これはほとんどの人があなたに言うのを忘れているステップです]

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

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

真実は、Pythonは、それが望んでいる二段階以上のいつでも行うにわたるループオブジェクトの内容-それはforループとすることができるように、それはまたようなコードとすることができるotherlist.extend(mylist)otherlistPythonのリストです)。

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

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

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

forループは、処理するオブジェクトの種類を認識しないことに注意してください。ループは反復子プロトコルに従うだけであり、が呼び出されたときに、項目ごとに取得できますnext()。一つ一つ自分のアイテムを返す、辞書が返すリストに、内蔵のキーを一つずつ、ファイルが返す行をさんはどこもあること...など、一つ一つや発電機のリターンyield:で来ます

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yieldステートメントの代わりに、最初のreturnステートメントf123()のみに3つのステートメントがある場合、実行され、関数は終了します。しかしf123()、通常の機能ではありません。ときにf123()呼ばれて、それはしません歩留まり文で値のいずれかを返します!ジェネレーターオブジェクトを返します。また、関数は実際には終了せず、一時停止状態になります。場合forジェネレータオブジェクトをループするループ試みの後に、機能は非常に次の行で、その停止状態から再開しyield、それが以前から返され、この場合には、コードの次の行を実行するyield文を、そして戻り、次のようなもの項目。これは、関数が終了するまで発生しStopIterationます。その時点でジェネレータが発生し、ループが終了します。

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

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

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


20
「yieldステートメントのある関数を見つけたら、この簡単なトリックを適用して何が起こるかを理解してください。」sendこれは、ジェネレーターの要点であるジェネレーターに入ることができるという事実を完全に無視していませんか?
DanielSank 2017年

10
「これはforループの可能性がありますが、otherlist.extend(mylist)「->これは不正解です。extend()リストをインプレースで変更し、イテラブルを返しません。ループオーバーしようotherlist.extend(mylist)とすると、暗黙的にが返され、ループオーバーできないTypeErrorため、で失敗しextend()ます。NoneNone
Pedro

4
@pedroその文を誤解しています。これは、Pythonが実行時に2つの上記のステップをmylist(ではなくotherlist)実行することを意味しますotherlist.extend(mylist)
本日

555

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

イテレータとは、next()メソッドを持つオブジェクトを表す洗練された用語です。したがって、yield-ed関数は次のようになります。

元のバージョン:

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

それはもっと理にかなっていますか、それともあなたをもっと混乱させていますか?:)

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


1
__getitem__の代わりに定義できます__iter__。例:class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)、次のように出力されます:
0、10、20

17
私はPython 3.6でこの例を試しましたがiterator = some_function()、を作成した場合、変数にiteratornext()もう呼び出される関数がなく、__next__()関数しかありません。私はそれを言及するだろうと思った。
ピーター

作成したforループ実装は、インスタンス化されたインスタンスのインスタンスであるの__iter__メソッドをどこで呼び出しiteratorますitか?
系統的

455

yieldキーワードは2つの単純な事実に縮小されています。

  1. コンパイラが関数内の任意の場所でyieldキーワードを検出した場合、その関数はステートメントを介して戻りません。代わりに、ジェネレーターと呼ばれる遅延「保留リスト」オブジェクトをすぐに返しますreturn
  2. ジェネレータは反復可能です。イテラブルとは何ですか?listorまたはsetor rangeまたは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]]

makeRangePythonのような関数を定義してみましょうrangemakeRange(n)RETURNS A GENERATORを呼び出す:

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.from_iterableではなく、必ず使用chainしてください)。たとえば、ジェネレータを使用して、などの無限に長い遅延リストを実装することもできますitertools.count()。独自のを実装することも、whileループのキーワードをdef enumerate(iterable): zip(count(), iterable)使用して実装することもでき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で言うと、反復可能オブジェクトは、リストのように「forループの概念を理解する」任意のオブジェクトで[1,2,3]あり、イテレータは、要求されたforループの特定のインスタンスです[1,2,3].__iter__()発電機は、それが(関数の構文で)書かれていた方法を除き、任意のイテレータとまったく同じです。

リストからイテレータをリクエストすると、新しいイテレータが作成されます。ただし、イテレーターにイテレーターを要求すると(めったにそうしません)、それ自体のコピーが提供されます。

したがって、万が一、このようなことを実行できなかった場合...

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

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


378

何をしないyieldキーワードはPythonでいますか?

回答概要/概要

  • を持つ関数はyield、呼び出されるとGeneratorを返します。
  • ジェネレーターはイテレータープロトコルを実装しているため、イテレーターです
  • ジェネレータには情報を送信することもでき、概念的にはコルーチンになります。
  • Python 3では、を使用して、あるジェネレーターから別のジェネレーターに双方向で委任できますyield from
  • (付録では、上位のものを含むいくつかの回答を批評returnし、ジェネレーターでのの使用について説明しています。)

発電機:

yield関数定義の内部でのみ有効であり、関数定義に含めるとyieldジェネレーターが返されます。

ジェネレータのアイデアは、さまざまな実装を持つ他の言語(脚注1を参照)に由来しています。Pythonのジェネレータでは、コードの実行は、yieldの時点で凍結されます。ジェネレーターが呼び出されると(メソッドについては以下で説明します)、実行が再開され、次の収量でフリーズします。

yield簡単な方法を提供イテレータプロトコルを実装する:以下の2つの方法で定義され、 __iter__及びnext(パイソン2)または__next__(Pythonの3)。これらのメソッドはどちらもIteratorcollectionsモジュールの抽象基本クラスで型チェックできるオブジェクトをイテレータにします。

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

ジェネレータタイプはイテレータのサブタイプです。

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

必要に応じて、次のようにタイプチェックできます。

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

の機能は、Iterator 使い尽くされると、それを再利用したりリセットしたりできないことです。

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

その機能を再び使用したい場合は、別のものを作成する必要があります(脚注2を参照)。

>>> list(func())
['I am', 'a generator!']

たとえば、プログラムでデータを生成できます。

def func(an_iterable):
    for item in an_iterable:
        yield item

上記の単純なジェネレーターは、以下のものとも同等です。Python3.3以降(およびPython 2では使用不可)、使用できますyield from

def func(an_iterable):
    yield from an_iterable

ただし、yield fromサブコジェネレーターとの協力的な委任に関する次のセクションで説明するサブジェネレーターへの委任も可能です。

コルーチン:

yield データをジェネレータに送信できるようにする式を作成します(脚注3を参照)

次に例を示しreceivedます。ジェネレーターに送信されるデータを指す変数に注意してください。

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

まず、組み込み関数でジェネレータをキューに入れる必要がありますnext。使用しているPythonのバージョンに応じて、適切なnextまたは__next__メソッドを呼び出します。

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

これで、ジェネレータにデータを送信できます。(送信Noneは呼び出しと同じnextです。):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

サブコルーチンへの協力的な委任 yield from

yield fromPython 3で利用できることを思い出してください。これにより、コルーチンをサブコルーチンに委任できます。

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

これで、機能をサブジェネレーターに委任することができ、上記のようにジェネレーターで使用できます。

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

の正確なセマンティクスについて詳しくはyield fromPEP 380を参照してください。

その他の方法:閉じて投げる

closeこの方法は、上げGeneratorExit機能の実行が凍結された時点で。これもによって呼び出される__del__ため、次のように処理するクリーンアップコードを配置できますGeneratorExit

>>> my_account.close()

ジェネレータで処理したり、ユーザーに伝播したりできる例外をスローすることもできます。

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

結論

私は次の質問のすべての側面をカバーしたと思います:

何をしないyieldキーワードはPythonでいますか?

それはyield多くのことを行います。これにさらに徹底した例を追加できると確信しています。もっと知りたい、または建設的な批判がある場合は、以下のコメントでお知らせください。


付録:

トップ/受け入れられた回答の批評**

  • 例としてリストを使用するだけで、iterableを作成する方法について混乱しています。上記の参考文献を参照してください。ただし、要約すると、反復可能オブジェクトには反復子を__iter__返すメソッドがあります反復子は、提供.next(パイソン2又は.__next__暗黙によって呼び出される(Pythonの3)の方法、forそれが提起されるまでループStopIterationし、それがないと、それはそうし続けるであろう。
  • 次に、ジェネレータ式を使用して、ジェネレータとは何かを説明します。ジェネレーターは単にイテレーターを作成するための便利な方法であるため、問題を混乱させるだけであり、まだそのyield部分に到達していません。
  • 発電機の枯渇を制御する彼を呼び出し.next、代わりに彼は組み込み関数を使用する必要がある場合、方法next。彼のコードはPython 3では機能しないため、これは間接参照の適切なレイヤーになります。
  • Itertools?これは何をするのかとは全く関係がありませんでしたyield
  • Python 3 yield新機能とともに提供されるメソッドについての議論はありませんyield fromトップ/受け入れられた答えは非常に不完全な答えです。

yieldジェネレータ式または理解で示唆する回答の批評。

文法は現在、リスト内包表記の任意の式を許可しています。

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

収量は式であるため、理解やジェネレーターの式で使用することは、特に良いユースケースを引用していないにもかかわらず、興味深いものとしてうんざりしています。

CPythonコア開発者は、その許容範囲の廃止について話し合っています。メーリングリストからの関連する投稿は次のとおりです。

2017年1月30日19:05に、ブレットキャノンは次のように書いています。

日曜日、2017年1月29日16:39に、クレイグロドリゲスは次のように書いています。

どちらの方法でも大丈夫です。物事をPython 3でそのままにしておくのは良くありません、私見。

私の投票は、構文から期待どおりの結果が得られないため、SyntaxErrorです。

現在の振る舞いに依存するコードは非常に賢くて保守できないため、これは最終的には賢明な場所だと思います。

そこにたどり着くという観点から、私たちはおそらく次のことを望みます:

  • 3.7のSyntaxWarningまたはDeprecationWarning
  • 2.7.xでのPy3k警告
  • 3.8のSyntaxError

乾杯、ニック。

-ニックコグラン| gmail.comのncoghlan | オーストラリア、ブリスベン

さらに、これが良いアイデアではないという方向を指しているように見える未解決の問題(10544)があります(Pythonで記述されたPython実装であるPyPyはすでに構文警告を引き起こしています)。

結論として、CPythonの開発者が別のことを言うまで:ジェネレータ式や理解を入れないでくださいyield

returnジェネレーターのステートメント

ではPythonの2

ジェネレーター関数では、returnステートメントにを含めることはできませんexpression_list。そのコンテキストでは、ベアreturnはジェネレータが完了し、発生することStopIterationを示します。

expression_list基本的には、カンマで区切られた式の任意の数である-基本的に、Pythonの2に、あなたがして発電機を停止することができますreturnが、あなたは値を返すことはできません。

ではPythonの3

ジェネレーター関数では、returnステートメントはジェネレーターが完了し、発生することStopIterationを示します。戻り値(存在する場合)は、構築のための引数として使用されStopIterationStopIteration.value属性になります。

脚注

  1. CLU、Sather、およびIcon言語は、Pythonにジェネレーターの概念を導入する提案で参照されました。一般的な考え方は、関数は内部状態を維持し、ユーザーの要求に応じて中間データポイントを生成できるということです。これにより、一部のシステムでは利用できないPythonスレッドなどの他のアプローチよりもパフォーマンス優れていることが約束されました。

  2. これは、たとえば、xrangerangePython 3の)オブジェクトIteratorは反復可能であっても、再利用できるため、sではないことを意味します。リストと同様に、それらの__iter__メソッドはイテレータオブジェクトを返します。

  3. yieldもともとはステートメントとして導入されました。つまり、コードブロックの行の先頭にしか出現できませんでした。ここでyield、利回り式を作成します。 https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt この変更は、ユーザーがデータを受信する場合と同じように、ユーザーがジェネレーターにデータを送信できるようにするために提案されました。データを送信するには、データを何かに割り当てることができなければなりません。そのためには、ステートメントが機能しません。


328

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

コードの場合、関数get_child_candidatesはイテレータのように機能するため、リストを拡張すると、一度に1つの要素が新しいリストに追加されます。

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


107
これは近いですが、正しくありません。yieldステートメントを含む関数を呼び出すたびに、新しいジェネレーターオブジェクトが返されます。最後のyieldの後で実行が再開されるのは、そのジェネレーターの.next()メソッドを呼び出したときだけです。
クロシュ2008年

239

もう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);

これは本当にいくつかの問題を単純化するのに役立ち、いくつかのことを扱いやすくします。


233

最小限の作業例を好む人のために、このインタラクティブなPythonセッションについて瞑想します。

>>> 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

209

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代わりにそれぞれのピースが作成されます。

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


yieldある甘いと言うへの道

一連のものを構築する

同じ動作:

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

異なる動作:

歩留まりはシングルパスです。反復できるのは1回だけです。関数にyieldがある場合、それをジェネレーター関数と呼びます。そしてイテレータはそれが返すものです。それらの用語は明らかになっています。コンテナーの利便性は失われますが、必要に応じて計算され、任意に長くなるシリーズの能力が得られます。

収量は怠惰であり、計算を先送りにします。収量が含まれる関数は、呼び出したときに実際はまったく実行されません。中断したところを記憶するイテレータオブジェクトを返します。next()イテレータを呼び出すたびに(これはforループで発生します)、次のyieldまで数インチ実行します。returnStopIterationを上げてシリーズを終了します(これは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]

両方の意味が適用されるyieldため、単語の見事な選択:

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

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

利回り —道を譲るか、放棄する(政治権力の場合と同様)

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


194

利回りは発電機を提供します。

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万のリストが必要な場合はどうでしょうか。これは巨大なメモリを食べる人であるだけでなく、関数が呼び出されたときにビルドするのに多くの時間がかかります。

2番目のケースでbarは、ジェネレーターを提供します。ジェネレータは反復可能です。つまり、forループなどで使用できますが、各値にアクセスできるのは1回だけです。すべての値が同時にメモリに保存されるわけではありません。ジェネレーターオブジェクトは、最後に呼び出したときにループしていた場所を「記憶」します。つまり、反復可能オブジェクトを使用して(たとえば)500億までカウントする場合、すべてを500億までカウントする必要はありません。一度に500億の数値を保存して、カウントします。

繰り返しますが、これはかなり不自然な例です。本当に500億まで数えたい場合は、おそらくitertoolsを使用します。:)

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


ただの注意-Python 3ではrange、リストの代わりにジェネレータも返されるので、__repr__/ __str__がオーバーライドされてより良い結果が表示されることを除いて、この場合も同様のアイデアが表示されrange(1, 10, 2)ます。
それはNotALieです。

189

ジェネレーターを返します。私は特にPythonに慣れていませんが、C#のイテレータブロックと同じようなものだと思います。

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


167

ジェネレーターの使用方法を説明する多くのすばらしい回答の中には、まだ与えられていないと感じている回答のタイプがあります。ここにプログラミング言語理論の答えがあります:

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

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

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

継続渡しスタイル(CPS)では、継続は通常の関数(関数が最初のクラスである言語のみ)であり、プログラマーが明示的に管理し、サブルーチンに渡します。このスタイルでは、プログラムの状態は、スタック上のどこかにある変数ではなく、クロージャー(およびたまたまエンコードされている変数)によって表されます。制御フローを管理する関数は継続を引数として受け入れ(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))。この例は、ジェネレーターの能力を示しています。イテレーターが何かある場合、ジェネレーターはイテレーションの状態を保存できます。

繰り返します:継続はプログラムのスタックの状態を保存でき、ジェネレーターは反復の状態を保存できます。つまり、継続はジェネレーターよりもはるかに強力ですが、ジェネレーターははるかに簡単です。これらは、言語デザイナーが実装しやすく、プログラマーが使用しやすくなっています(書き込む時間があれば、継続とcall / 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キーワードを使用せずにジェネレーターオブジェクトを実装してみてください。


152

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

数字のシーケンスを操作したいのですが、そのシーケンスの作成に自分を煩わせたくないので、やりたい操作だけに集中したいと思います。だから、私は次のことをします:

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

これはジェネレーターが行うことです(を含む関数yield)。実行を開始し、を実行するたびに一時停止yieldし、.next()値を要求されると、最後のポイントから続行します。これは、Pythonのイテレータプロトコルとの設計により完全に適合します。これは、値を順次要求する方法を説明します。

イテレータプロトコルの最も有名なユーザーは、forPython のコマンドです。したがって、次のことを行うたびに:

for item in sequence:

上記のようなsequenceリスト、文字列、辞書、またはジェネレーターオブジェクトであるかどうかは関係ありません。結果は同じです。シーケンスからアイテムを1つずつ読み取ります。

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

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


130

多くの答えが、なぜを使用しyieldてジェネレータを作成するのかを示していますが、の使用法は他にもありますyield。コルーチンを作成するのは非常に簡単で、2つのコードブロック間で情報を受け渡すことができます。を使用yieldしてジェネレーターを作成する方法についてすでに説明した優れた例については、繰り返し説明しません。

yield次のコードでa が何をするかを理解するのに役立つように、指を使って、を含むコード全体のサイクルをトレースできますyield。指がを押すたびyieldに、a nextまたはa sendが入力されるのを待つ必要があります。a nextが呼び出されると、yield…の右側のコードyieldが評価されて呼び出し元に返されるまでコードをトレースし、その後待機します。ときにnext再び呼び出され、あなたはコードを別のループを実行します。しかし、あなたはコルーチンで、ということに注意してくださいよyieldまた一緒に使用することができsend、発信者からの値を送信しますどの... もたらす機能。もし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()

可愛い!トランポリン(Lispの感覚で)。しばしばそれらを見ることはありません!
00prometheus

129

別のyield使用法と意味があります(Python 3.3以降)。

yield from <expr>

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

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

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

さらに、これが導入されます(Python 3.5以降):

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

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


117

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

あなたはそのreturn声明を学んだと思います。

アナロジーとして、returnそしてyield双子です。return「戻ると停止」を意味するのに対し、「yield」は「戻るが続行する」を意味します

  1. でnum_listを取得してみてくださいreturn
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どちらが1回実行して停止するかと比較すると、yield計画した実行時間が実行されます。あなたは解釈することができるreturnようreturn one of them、そしてyieldとしてreturn all of them。これはと呼ばれiterableます。

  1. yieldステートメントを次のように書き換えることができるもう1つのステップreturn
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出力」から取得できるのは一度だけです。そのため、にgenerator表示されているように、新しい名前オブジェクトがありますOut[11]: <generator object num_list at 0x10327c990>

結論として、それを暗示するメタファーとして:

  • returnそしてyield双子です
  • listそしてgenerator双子です

これは理解できますが、1つの大きな違いは、関数/メソッドで複数のイールドを使用できることです。類推はその時点で完全に崩れます。Yieldは関数内での場所を記憶しているため、次回next()を呼び出すと、関数は次の関数に進みますyield。これは重要だと思いますし、表現すべきです。
マイクS

104

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)

97

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

また、yieldジェネレータ関数での使用の二重として、コルーチンで使用できることに注意してください。コードスニペットと(yield)は異なりますが、関数の式として使用できます。呼び出し元がメソッドを使用してメソッドに値を送信するとsend()、コルーチンは次の(yield)ステートメントが検出されるまで実行されます。

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


97

プログラミングの観点からは、イテレータはサンクとして実装されます。

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

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
->

84

以下に簡単な例を示します。

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


あなたは正しいです。しかし、「収量」の動作を確認することは、フローにどのような影響を与えるのでしょうか。数学の名の下にアルゴリズムを変更できます。「収量」の異なる評価を取得するのに役立ちますか?
Engin OZTURK 2018

68

これが何をするかの精神的なイメージです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(..)、上記のメンタルモデルが信頼できることが確認できます。


59

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

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

次のようにコードで使用できます。

for line in getNextLines():
    doSomeThing(line)

実行制御転送の問題

実行制御はfor、yieldが実行されるときにgetNextLines()からループに転送されます。したがって、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"

59

それが何であるかを理解する簡単な例: 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

5
その出力について確信がありますか?print(i, end=' ')?を使用してその印刷ステートメントを実行した場合は、1行にしか印刷されません。それ以外の場合、デフォルトの動作では各番号が新しい行に
配置されると思い

@ user9074332、そのとおりですが、理解を容易にするために1行で書かれています
Gavriel Cohen

57

(私の以下の回答は、Pythonジェネレーターの使用の観点からのみ述べており、スタックとヒープ操作のいくつかのトリックを伴うジェネレーターメカニズムの基礎となる実装ではありません。)

ときyieldの代わりに使用されているreturnPythonの関数で、その関数が呼び出さ何か特別なになっていますgenerator function。その関数はgeneratorタイプのオブジェクトを返します。キーワードは、特別な機能を処理するためのpythonコンパイラに通知するためのフラグです。通常の関数は、値が返されると終了します。しかし、コンパイラーの助けを借りて、ジェネレーター関数再開可能であると考えることができます。つまり、実行コンテキストが復元され、最後の実行から実行が続行されます。returnを明示的に呼び出すまでは、例外が発生するか(これもイテレータプロトコルの一部です)、または関数の最後に到達します。私は約参照の多く見られるが、この1をyieldStopIterationgeneratorからfunctional 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反復子が返されます。

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


54

要約すると、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に戻り、ジェネレーターが関数の最後にジャンプするまで、その方法が継続されます。


53

収量はオブジェクトです

return機能的には単一の値を返します。

関数が大量の値のセットを返すようにしたい場合は、を使用しますyield

さらに重要なのyieldは、障壁です。

CUDA言語のバリアのように、完了するまで制御は渡されません。

つまり、最初からヒットするまで関数のコードを実行しますyield。次に、ループの最初の値を返します。

次に、他のすべての呼び出しで、関数に記述したループがもう一度実行され、返される値がなくなるまで次の値が返されます。


52

多くの人がではreturnなくを使用していますyieldが、場合によってyieldはより効率的で扱いやすい場合もあります。

以下はyield間違いなく最適な例です:

戻り値(関数内)

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

収量(関数内)

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)

どちらの関数も同じことを行いますが、yield5行ではなく3行を使用しており、心配する変数が1つ少なくなっています。

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

出力

ご覧のとおり、両方の関数は同じことを行います。唯一の違いはreturn_dates()、リストとyield_dates()ジェネレーターです。

実際の例は、ファイルを1行ずつ読み取るようなもの、または単にジェネレータを作成する場合などです。


43

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



36

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>

これは、yieldPythonにジェネレーター(つまり、オンデマンドで値を生成するオブジェクト)を作成したいというシグナルが存在するためです。

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

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

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

間接的fibに、forループ、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呼び出しを使用してみましょう(print "text"Python 2ではifで置き換えてください)。

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!'

引用されていない結果が出力されます。引用された結果は、から返されるものですyieldnextもう一度電話してください:

>>> 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ループのために)再度実行されます。

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