11880

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

47 に答える 47

16781

何をするのかを理解するには、ジェネレーターyieldとは何かを理解する必要があります。ジェネレーターを理解する前に、iterablesを理解する必要があります。

イテラブル

リストを作成すると、その項目を 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 create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator 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については、この記事で詳しく説明しています。

于 2008-10-23T22:48:44.797 に答える
2322

理解への近道yield

ステートメントを含む関数が表示されたら、次のyield簡単なトリックを適用して、何が起こるかを理解してください。

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

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

Iterables、Iterators、および Generators を混同しないでください

まず、反復子プロトコル- 書くとき

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

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

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

    Call iter(mylist)-> メソッドを含むオブジェクトを返しますnext()(または__next__()Python 3 の場合)。

    [これは、ほとんどの人が忘れているステップです]

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

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

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

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

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

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

ループは、処理しているオブジェクトの種類を認識していないことに注意してください。forループはイテレータ プロトコルに従っているだけで、 を呼び出すたびに項目を次々と取得できますnext()。組み込みのリストは項目を 1 つずつ返し、辞書はキーを 1 つずつ返し、ファイルはを 1 つずつ返します。そして、ジェネレーターは戻ります...まあ、ここでyield出番です:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

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

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

于 2008-10-25T21:22:30.393 に答える
681

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

next()イテレータは、メソッドを持つオブジェクトを意味する単なる風変わりな用語です。したがって、yield された関数は次のようになります。

元のバージョン:

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

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

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

于 2008-10-23T22:28:41.187 に答える
562

このyieldキーワードは、次の 2 つの単純な事実に要約されます。

  1. コンパイラが関数内の任意の場所yieldでキーワードを検出した場合、その関数はステートメントを介して返されなくなります。代わりに、ジェネレーターと呼ばれる遅延した「保留中のリスト」オブジェクトをすぐに返します。return
  2. ジェネレーターは反復可能です。イテラブルとは?これは、 or または or または dict-view のようなものlistsetrange特定の順序で各要素にアクセスするための組み込みプロトコルを備えています

簡単に言うと、ジェネレーターは怠惰な増分保留リストであり、yieldステートメントを使用すると、関数表記を使用して、ジェネレーターが増分的に吐き出すリスト値をプログラムできます。

generator = myYieldingFunction(...)  # basically a list (but lazy)
x = list(generator)  # evaluate every element into a list

   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

基本的に、yieldステートメントが検出されるたびに、関数は一時停止してその状態を保存し、Python イテレータ プロトコルに従って「'リスト' 内の次の戻り値」を発行します (繰り返し呼び出しnext()とキャッチを行う for ループのような構文構造に)。StopIteration例外など)。ジェネレーター式を持つジェネレーターに遭遇したことがあるかもしれません。ジェネレーター関数は、一時停止されたジェネレーター関数に引数を戻すことができ、それらを使用してコルーチンを実装できるため、より強力です。それについては後で詳しく説明します。


基本的な例 ('list')

makeRangePython の のような関数を定義しましょうrangemakeRange(n)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]

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

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

# return a list                  #  # return a generator
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
    return TO_RETURN             # 

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

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


ジェネレーターの使用方法

iterable はリスト内包表記の最後の部分であり、すべてのジェネレーターは iterable であるため、次のように使用されることがよくあります。

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

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

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


舞台裏

これが「Python 反復プロトコル」の仕組みです。つまり、あなたがするときに何が起こっているのかlist(makeRange(5)). これは、以前に「怠惰な増分リスト」として説明したものです。

>>> x=iter(range(5))
>>> next(x)  # calls x.__next__(); x.next() is deprecated
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()


コルーチン

コルーチンの例:

def interactiveProcedure():
    userResponse = yield makeQuestionWebpage()
    print('user response:', userResponse)
    yield 'success'

coroutine = interactiveProcedure()
webFormData = next(coroutine)  # same as .send(None)
userResponse = serveWebForm(webFormData)

# ...at some point later on web form submit...

successStatus = coroutine.send(userResponse)

細目

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

Python で言えば、iterableは list のような「for ループの概念を理解する」任意のオブジェクトで[1,2,3]あり、iteratorは のような要求された for ループの特定のインスタンスです[1,2,3].__iter__()ジェネレーターは、記述方法 (関数構文) を除いて、任意のイテレーターとまったく同じです。

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

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

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

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

于 2011-06-19T06:33:58.953 に答える
501

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

回答概要・まとめ

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

ジェネレーター:

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

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

yieldと(Python 2) または(Python 3)の 2 つのメソッドで定義される iterator protocolを実装する簡単な方法を提供します。これらのメソッドは両方とも、オブジェクトを、モジュールの抽象基本クラスで型チェックできる反復子にします。__iter__next__next__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

上記の単純なジェネレーターは、以下のものと同等でもあります - Python 3.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)

まず、組み込み関数 を使用してジェネレーターをキューに入れる必要がありますnextnext使用している Python のバージョンに応じて、適切なor__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):
    # must receive deposited value from .send():
    under_management = yield                   # yield None to start.
    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'''
            raise
        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'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

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

my_manager = money_manager(.06)
my_account = investment_account(1000, my_manager)
first_year_return = next(my_account) # -> 60.0

次に、アカウントにさらに 1,000 を追加し、アカウントの収益 (60.0) をシミュレートします。

next_year_return = my_account.send(first_year_return + 1000)
next_year_return # 123.6

PEP 380yield fromの正確なセマンティクスについて詳しく読むことができます。

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

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 6, in money_manager
  File "<stdin>", line 2, in <module>
ValueError

結論

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

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

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


付録:

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

  • リストを例として使用するだけで、 iterableを作るものについて混乱しています。上記の参考文献を参照してください。要約すると、 iterable にはiterator__iter__を返すメソッドがあります。イテレータは(Python 2 または(Python 3) メソッドを提供します。このメソッドは、 が発生するまでループによって暗黙的に呼び出され、一度発生すると、引き続きそうします。.next.__next__forStopIteration
  • 次に、ジェネレーター式を使用して、ジェネレーターとは何かを記述します。ジェネレーターは単にiteratorを作成するための便利な方法であるため、問題を混乱させるだけであり、まだそのyield部分に到達していません。
  • ジェネレーターの枯渇を制御するでは.next、代わりに組み込み関数を使用する必要があるときに、メソッドを呼び出しますnext。彼のコードは Python 3 では機能しないため、これは適切な間接レイヤーになります。
  • イターツール?これは、何が何をするのかとはまったく関係がありませんでした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

yield は式であるため、特に優れたユースケースは挙げていませんが、内包表記やジェネレーター式で使用するのは興味深いと宣伝されています。

CPython のコア開発者は、その許可を非推奨にすることについて議論しています。メーリングリストからの関連する投稿は次のとおりです。

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

2017 年 1 月 29 日日曜日の 16:39 に Craig Rodrigues は次のように書いています。

私はどちらのアプローチでもOKです。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. そのコンテキストでは、barereturnはジェネレーターが完了し、発生することを示しますStopIteration

Anexpression_listは基本的に、コンマで区切られた任意の数の式です。基本的に、Python 2 では、ジェネレーターをreturnで停止できますが、値を返すことはできません。

Python 3 の場合:

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

脚注

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

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

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

于 2015-06-25T06:11:11.867 に答える
414

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

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

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

于 2008-10-23T22:24:03.757 に答える
294

言及すべきもう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);

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

于 2008-10-24T08:44:08.123 に答える
283

最小限の実用的な例を好む人のために、このインタラクティブな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
于 2013-01-18T17:25:17.497 に答える
263

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

異なる動作:

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

Yield はlazyであり、計算を先延ばしにします。yield を含む関数は、呼び出しても実際にはまったく実行されません。中断した場所を記憶するiterator オブジェクトを返します。イテレータを呼び出すたび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ため、言葉の素晴らしい選択:

yield — 生産または提供 (農業のように)

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

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

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

于 2016-03-25T13:21:44.003 に答える
231

Yield はジェネレーターを提供します。

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ループなどで使用できますが、各値には一度しかアクセスできません。また、すべての値が同時にメモリに保存されるわけではありません。ジェネレーターオブジェクトは、最後に呼び出したときのループの場所を「記憶」しています。このように、イテラブルを使用して (たとえば) 500 億までカウントする場合、500 億までカウントする必要はありません。一度に 500 億個の数字を保存してカウントします。

繰り返しますが、これは非常に不自然な例です。本当に 500 億までカウントしたい場合は、おそらく itertools を使用するでしょう。:)

これは、ジェネレーターの最も単純な使用例です。あなたが言ったように、ある種のスタック変数を使用する代わりに、yield を使用して呼び出しスタックを介して物事をプッシュすることで、効率的な順列を記述するために使用できます。ジェネレーターは、特殊なツリー トラバーサルやその他のあらゆる用途にも使用できます。

于 2013-01-16T06:42:09.097 に答える
224

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

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

于 2008-10-23T22:26:06.810 に答える
201

ジェネレーターの使用方法を説明する多くの優れた回答の中で、まだ与えられていないと思うタイプの回答が 1 つあります。プログラミング言語理論の答えは次のとおりです。

Pythonのyieldステートメントはジェネレーターを返します。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)

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

この投稿の残りの部分では、一般性を失うことなく、継続を CPS として概念化します。これは、理解しやすく読みやすいためです。


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

def f():
  while True:
    yield 4

これは明らかに、動作が明確に定義された合理的な iterable です。ジェネレーターがそれを反復するたびに、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しかし、何が起こっているのかを理解するための演習として、継続渡しスタイルを使用して、キーワードを使用せずにジェネレーター オブジェクトを実装してみてください。

于 2013-04-04T14:56:19.677 に答える
190

これは平易な言葉の例です。高レベルの人間の概念と低レベルの Python の概念との間の対応を提供します。

数列を操作したいのですが、その数列の作成に煩わされたくありません。やりたい操作だけに集中したいのです。だから、私は次のことをします:

  • 私はあなたに電話して、特定の方法で計算された数列が欲しいと伝え、アルゴリズムが何であるかを知らせます.
    このステップはdef、ジェネレーター関数、つまり を含む関数を iningすることに対応しyieldます。
  • しばらくして、「OK、数列を教えて」と言います。
    このステップは、ジェネレーター オブジェクトを返すジェネレーター関数の呼び出しに対応します。まだ数字を教えていないことに注意してください。紙と鉛筆をつかむだけです。
  • 「次の番号を教えて」とあなたに尋ねると、あなたは最初の番号を教えてくれます。その後、私が次の番号を尋ねるのを待ちます。自分がどこにいたか、すでに言った数字は何か、次の数字は何かを覚えておくのはあなたの仕事です。詳細は気にしません。このステップは、ジェネレーター オブジェクトの
    呼び出しに対応します。next(generator)
    (Python 2 では.next、ジェネレーター オブジェクトのメソッドでした。Python 3 では、 という名前になっていますが、それを呼び出す適切な方法は、 and と同じよう.__next__に組み込み関数を使用することです)next()len().__len__
  • …まで、前のステップを繰り返します…</li>
  • 最終的に、あなたは終わりを迎えるかもしれません。あなたは私に番号を教えてくれません。あなたはただ叫ぶだけです。
    このステップは、ジェネレーター オブジェクトがジョブを終了し、StopIteration例外を発生させることに対応します。
    ジェネレーター関数は例外を発生させる必要はありません。関数が終了するか、return.

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

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

for item in sequence:

sequence上で説明したように、リスト、文字列、辞書、ジェネレーターオブジェクトのいずれであっても問題ありません。結果は同じです。シーケンスから項目を 1 つずつ読み取ります。

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

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

于 2008-10-24T00:36:05.190 に答える
159

を使用してジェネレーターを作成する理由は多くの回答で示されていyieldますが、 にはさらに多くの用途がありますyield。2 つのコード ブロック間で情報を受け渡すコルーチンを作成するのは非常に簡単です。yieldを使用してジェネレータを作成することについて、既に示した優れた例については繰り返しません。

yield次のコードでa が何をするかを理解するために、指を使ってyield. 指が に当たるたびに、 aまたは aが入力されるyieldのを待つ必要があります。aが呼び出されると、コードをトレースして、... の右側のコードが評価され、呼び出し元に返されます。その後、待機します。が再び呼び出されると、コードを通じて別のループを実行します。ただし、コルーチンでは、呼び出し元から生成関数に値を送信する …とともに使用することもできます。aが与えられた場合、nextsendnextyieldyieldnextyieldsendsendyield送信された値を受信し、それを左側に吐き出します...その後、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()
于 2014-02-04T02:27:35.200 に答える
155

別のyield用途と意味があります (Python 3.3 以降):

yield from <expr>

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

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

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

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

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

コルーチンが通常のジェネレーターと混同されるのを避けるため (現在yieldは両方で使用されています)。

于 2014-07-24T21:15:29.810 に答える
129

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

同時実行用のイテレータ、ジェネレータ、スレッド プールなどをサンクとして実装するには、ディスパッチャを持つクロージャ オブジェクトに送信されるメッセージを使用し、ディスパッチャが "messages" に応答します

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

この計算を実装する方法はたくさんあります。私はミューテーションを使用しましたが、現在の値と次のyielderを返す(参照を透過的にする)ことにより、ミューテーションなしでこの種の計算を行うことができます。Racket は、いくつかの中間言語で初期プログラムの一連の変換を使用します。そのような書き換えの 1 つは、yield 演算子をいくつかの言語でより単純な演算子に変換します。

これは、R6RS の構造を使用して yield を書き換える方法のデモですが、セマンティクスは Python のものと同じです。これは同じ計算モデルであり、構文を変更するだけで Python の yield を使用して書き直すことができます。

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
->
于 2013-08-21T19:01:25.917 に答える
125

以下は、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)
于 2012-10-03T20:38:16.153 に答える
83

これが何をするかの精神的なイメージです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_codegi_frame凍結状態が保存される場所です。でそれらを調べるとdir(..)、上記のメンタル モデルが信頼できるものであることを確認できます。

于 2013-06-14T16:36:59.193 に答える
74

それが何であるかを理解するための簡単な例: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
于 2017-01-02T12:09:28.120 に答える
73

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

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

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

for line in getNextLines():
    doSomeThing(line)

実行制御転送の落とし穴

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"
于 2015-07-29T06:11:25.643 に答える
70

要約すると、ステートメントは関数を、元の関数の本体をラップyieldする a という特別なオブジェクトを生成するファクトリに変換します。generatorgenerator繰り返されると、次の関数に到達するまで関数を実行し、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 の場合は停止し、再びyield 'n'を実行すると、while条件がfalseを返し、ジェネレーターが関数の最後にジャンプするまで、その方法が続きます。

于 2016-10-13T13:43:40.877 に答える
66

収量はオブジェクトです

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

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

さらに重要なのyieldは、バリアです。

CUDA 言語のバリアのように、完了するまで制御を移しません。

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

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

于 2015-09-01T12:42:19.250 に答える
54

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

于 2015-11-18T19:37:29.577 に答える
51

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

于 2015-05-20T06:19:32.417 に答える
50

さらに別のTL;DR

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

イテレータ ジェネレータ:next()オンザフライで次の要素を計算します (コードを実行します)

yield/generator は、フローが複雑であっても、を呼び出すことによって、制御フローを外部から手動で実行する方法として見ることができます (ループを 1 ステップ継続するなど) 。next

:ジェネレーターは通常の機能ではありません。ローカル変数(スタック)のように以前の状態を記憶します。詳細な説明については、他の回答または記事を参照してください。ジェネレーターは1 回だけ反復できます。なしでもできますがyield、あまり良くないため、「非常に優れた」言語シュガーと見なすことができます。

于 2016-06-22T09:40:15.943 に答える
46

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>

これは、 の存在により、 generator、つまりオンデマンドで値を生成するオブジェクトをyield作成することを Python に知らせるためです。

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

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

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

間接的に、値を生成/生成するオブジェクトを期待するループ、イニシャライザ、イニシャライザ、またはその他のものに提供fibする場合、それ以上値を生成できなくなるまでジェネレータを「消費」します (そして、それが返されます)。 :forlisttuple

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一時停止します。これをよりよく示す例として、いくつかの呼び出しを使用してみましょう( Python 2 では if にprint置き換えます)。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!'

引用されていない結果が印刷されます。引用された結果は から返されるものです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ループのため)。

于 2016-02-20T17:41:32.807 に答える