「yield」キーワードは何をするのでしょうか?
質問
の用途は何ですか yield
Pythonのキーワード?それは何をするためのものか?
たとえば、このコードを理解しようとしています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) によって書かれました。これは完全なソースへのリンクです。 モジュールの mspace.
解決
yield
の機能を理解するには、ジェネレータとは何かを理解する必要があります。ジェネレーターを理解する前に、 iterables を理解する必要があります。
イテラブル
リストを作成すると、そのアイテムを1つずつ読むことができます。アイテムを1つずつ読み取ることを反復と呼びます:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
は iterable です。リスト内包表記を使用すると、リストが作成されるため、反復可能です:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
使用できるものすべて&quot; for ... in ...
&quot; onは反復可能です。 リスト
、 strings
、ファイル...
これらのイテラブルは、必要なだけ読むことができるので便利ですが、すべての値をメモリに保存します。多くの値がある場合、これは必ずしも必要なものではありません。
ジェネレーター
ジェネレーターはイテレーターであり、一種の反復可能な一度だけ反復できる。ジェネレーターはすべての値をメモリに保存するわけではなく、その場で値を生成します:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
[]
の代わりに()
を使用した以外はまったく同じです。ただし、ジェネレーターは1回しか使用できないため、 iを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
に達するまで実行され、その後llはループの最初の値を返します。次に、他の各呼び出しは、関数に記述したループをもう一度実行し、返す値がなくなるまで次の値を返します。
ジェネレーターは、関数が実行されると空と見なされますが、 yield
にヒットしなくなります。ループが終了したか、&quot; if / else&quot;
をもう満たさないことが原因である可能性があります。
コードの説明
ジェネレーター:
# 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]
ただし、コードではジェネレーターを取得します。これは、次の理由により優れています。
- 値を2回読み取る必要はありません。
- 多くの子供がいる可能性があり、それらすべてをメモリに保存したくない場合。
そして、Pythonがメソッドの引数がリストであるかどうかを気にしないので機能します。 Pythonはイテラブルを期待しているので、文字列、リスト、タプル、ジェネレーターで動作します!これはダックタイピングと呼ばれ、オンになっています
他のヒント
yield
を理解するためのショートカット
yield
ステートメントを含む関数が表示されたら、次の簡単なトリックを適用して、何が起こるかを理解します。
- 関数の先頭に
result = []
という行を挿入します。 - 各
yield expr
をresult.append(expr)
に置き換えます。 - 関数の下部に
return result
行を挿入します。 - はい-
yield
ステートメントはもうありません!コードを読んで理解する。 - 関数を元の定義と比較します。
このトリックは関数の背後にあるロジックのアイデアを与えるかもしれませんが、実際に yield
で起こることはリストベースのアプローチで起こることとは大きく異なります。多くの場合、yieldアプローチはメモリ効率が非常に高く、高速です。他の場合では、元の関数が正常に機能していても、このトリックにより無限ループに陥ります。続きを読んで詳細をご覧ください...
イテラブル、イテレータ、ジェネレータを混同しないでください
まず、イテレータプロトコル-作成時
for x in mylist:
...loop body...
Pythonは次の2つの手順を実行します。
-
mylist
の反復子を取得します:iter(mylist)
を呼び出す-&gt;これは、next()
メソッド(またはPython 3では__ next __()
)を持つオブジェクトを返します。[これはほとんどの人があなたに伝えることを忘れるステップです]
-
イテレータを使用してアイテムをループします:
ステップ1から返されたイテレータで
next()
メソッドを呼び出し続けます。next()
からの戻り値がx
に割り当てられます。ループ本体が実行されます。例外StopIteration
がnext()
内から発生した場合、イテレータに値がもうないことを意味し、ループは終了します。
実際には、Pythonはオブジェクトのコンテンツをループしたい場合はいつでも上記の2つのステップを実行します。したがって、forループの場合もありますが、 otherlistのようなコードの場合もあります.extend(mylist)
( otherlist
はPythonリストです)。
ここで mylist
は反復プロトコルを実装しているため、 iterable です。ユーザー定義クラスでは、 __ iter __()
メソッドを実装して、クラスのインスタンスを反復可能にすることができます。このメソッドは、イテレータを返す必要があります。イテレータは、 next()
メソッドを持つオブジェクトです。同じクラスに __ iter __()
と next()
の両方を実装し、 __ iter __()
が self
。これは単純なケースでは機能しますが、2つのイテレーターが同じオブジェクトを同時にループしたい場合は機能しません。
これがイテレータプロトコルであるため、多くのオブジェクトがこのプロトコルを実装しています。
- 組み込みリスト、辞書、タプル、セット、ファイル。
-
__ iter __()
を実装するユーザー定義のクラス。 - ジェネレータ。
for for
ループは、それがどの種類のオブジェクトを扱っているかを知らないことに注意してください-イテレータプロトコルに従うだけで、 next( )
。組み込みリストは項目を1つずつ返し、辞書は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
ループが
次のように考えてください:
イテレータは、 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
それはより理にかなっていますか、それとも単にあなたを混乱させますか? :)
これは、説明のために過度に単純化したものであることに注意してください。 :)
yield
キーワードは2つの単純な事実に削減されます:
- コンパイラーが関数内の
yield
キーワード anywhere を検出した場合、その関数はreturn
ステートメントを介して戻りません。 代わりに 、すぐには怠lazな「保留リスト」を返します。オブジェクトはジェネレータと呼ばれます - ジェネレータは反復可能です。 iterable とは何ですか?
list
またはset
または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]]
例
Pythonの range
のような関数 makeRange
を定義しましょう。 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-loopで yield
キーワードを使用して実装できます。
注意:ジェネレーターは、実際にはコルーチンの実装または非決定的プログラミングまたはその他のエレガントなもの。ただし、「遅延リスト」はここで紹介する視点は、最も一般的な使用法です。
舞台裏
これは、「Python反復プロトコル」の方法です。動作します。つまり、 list(makeRange(5))
を実行するとどうなりますか。これは、以前に「怠lazな増分リスト」と説明したものです。
>>> 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 は、「forループの概念を理解している」オブジェクトです。リスト [1,2,3]
のように、イテレータは [1,2,3]のような要求されたforループの特定のインスタンスです。 __iter __()
。 generator は、(関数構文を使用して)記述されている方法を除いて、イテレータとまったく同じです。
リストからイテレータをリクエストすると、新しいイテレータが作成されます。ただし、イテレータからイテレータをリクエストすると(めったにしない)、それ自体のコピーが提供されます。
したがって、万が一の場合にあなたが失敗している
Pythonで
yield
キーワードは何をしますか?
回答の概要/概要
-
yield
を持つ関数が呼び出されると、 ジェネレータを返します。 - ジェネレータは イテレータプロトコル 。したがって、それらを反復処理できます。
- ジェネレータは、送信情報にすることもでき、概念的にはコルーチンにします。
- Python 3では、
yield from
を使用して、あるジェネレータから別のジェネレータに委任できます。 - (付録では、上位の回答を含むいくつかの回答を批判し、ジェネレーターでの
return
の使用について説明します。)
ジェネレーター:
yield
は関数定義内でのみ有効であり、 yield
を関数定義に含めると、ジェネレータ。
ジェネレーターのアイデアは、さまざまな実装を持つ他の言語(脚注1を参照)から来ています。 Pythonのジェネレーターでは、コードの実行は frozen にあります収量のポイント。ジェネレーターが呼び出されると(メソッドについては以下で説明します)、実行が再開され、次の収量でフリーズします。
yield
は、
イテレータプロトコルを実装する簡単な方法、次の2つで定義メソッド:
__ iter __
および next
(Python 2)または __ next __
(Python 3)。両方の方法
オブジェクトをイテレータにして、 Iterator
Abstract Baseで型チェックできます
collections
モジュールのクラス。
>>> 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)
最初に、組み込み関数 でジェネレーターをキューに登録する必要があります次
。そうなる
バージョンに応じて、適切な next
または __ next __
メソッドを呼び出します
使用しているPython:
>>> 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 from
がPython 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 from
の正確なセマンティクスの詳細については、 PEP380。
その他のメソッド:closeおよびthrow
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
結論
次の質問のすべての側面をカバーしたと思います:
Pythonで
yield
キーワードは何をしますか?
yield
が非常に役立つことがわかりました。さらに追加できると確信しています
これの徹底的な例。もっと知りたい場合や建設的な批判がある場合は、コメントしてお知らせください
以下。
付録:
トップ/受け入れられた回答の批判**
- 例としてリストを使用するだけで、反復可能になる理由について混乱しています。上記の私の参考文献をご覧ください。しかし要約すると、イテレート可能オブジェクトには、イテレータを返す
__ iter __
メソッドがあります。 イテレーターは、.next
(Python 2または.__ next __
(Python 3)メソッドを提供します。これは、for
はStopIteration
を発生させるまでループし、いったん発生すると、継続して実行します。 - 次に、ジェネレータ式を使用して、ジェネレータとは何かを記述します。ジェネレーターは単にイテレーターを作成するための便利な方法であるため、問題を混乱させるだけであり、まだ
yield
の部分に到達していません。 - ジェネレータの枯渇の制御で、彼は
.next
メソッドを呼び出しますが、代わりに組み込み関数next
を使用する必要があります。彼のコードはPython 3では機能しないため、これは間接的な適切なレイヤーになります。 - Itertools?これは、
yield
の機能とはまったく関係ありませんでした。 -
yield
がPython 3の新機能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にクレイグ・ロドリゲスは次のように書いています:
どちらのアプローチでも構いません。 Python 3で物事をそのままにしておく よくない、私見。
あなたが期待するものを得ていないので、私の投票はSyntaxErrorである 構文。
コードのように、それが最終的に私たちにとって賢明な場所であることに同意します 現在の振る舞いに頼るのは本当に賢すぎる
yield
は return
のようなものです-(ジェネレータとして)あなたが伝えたものは何でも返します。違いは、次にジェネレータを呼び出すときに、実行が yield
ステートメントの最後の呼び出しから開始されることです。 returnとは異なり、 yieldが発生してもスタックフレームはクリーンアップされませんが、制御は呼び出し元に戻されるため、関数が次に呼び出されるときにその状態が再開されます。
コードの場合、関数 get_child_candidates
はイテレーターのように機能するため、リストを拡張すると、一度に1つの要素が新しいリストに追加されます。
list.extend
は、使い尽くされるまで反復子を呼び出します。投稿したコードサンプルの場合は、タプルを返してリストに追加する方がはるかに明確です。
もう1つ注意すべき点があります:生成される関数は実際に終了する必要はありません。このようなコードを書きました:
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);
これは本当にいくつかの問題を単純化するのに役立ち、いくつかのことを扱いやすくします。
最小限の作業例を好む人のために、このインタラクティブな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
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
してください。
これは私の最初の「aha」でした。歩留まりのある瞬間。
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が含まれる場合、ジェネレーター関数と呼びます。そして、イテレーターが返されます。これらの用語は明らかになっています。コンテナの利便性は失われますが、必要に応じて計算され、任意の長さのシリーズのパワーが得られます。
歩留まりは遅延で、計算を先送りします。 yieldを含む関数は、呼び出したときに実際にはまったく実行されません。 イテレータオブジェクト。中断した場所を記憶します。イテレータで 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]
両方の意味適用:
利回り&#8212;生産または提供(農業など)
...シリーズの次のデータを提供します。
利回り&#8212;道を譲るか放棄するか(政治権力のように)
...イテレータが進むまでCPUの実行を放棄します。
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
ループなどで使用できますが、各値には1回しかアクセスできません。また、すべての値が同時にメモリに保存されるわけではありません。ジェネレーターオブジェクトは「記憶」します。最後に呼び出したときにループ内にあった場所-このように、(たとえば)500億までカウントする反復可能なオブジェクトを使用している場合、一度に500億までカウントして50を保存する必要はありません数え切れないほどの数。
繰り返しますが、これはかなり不自然な例です。実際に500億をカウントしたい場合は、おそらくitertoolsを使用するでしょう。 :)
これは、ジェネレーターの最も単純な使用例です。あなたが言ったように、何らかのスタック変数を使用する代わりに、yieldを使用して呼び出しスタックを介して物事をプッシュアップして、効率的な順列を記述するために使用できます。ジェネレーターは、特殊なツリートラバーサルやその他のあらゆる方法にも使用できます。
ジェネレータを返しています。私はPythonに特に精通していませんが、 C#のイテレーターブロックと同じようなものだと思いますそれらに精通している場合。
重要な考え方は、コンパイラー/インタープリター/なんでもトリックを行うことです。そのため、呼び出し元に関する限り、next()を呼び出し続けることができ、ジェネレーターメソッドが一時停止。明らかに、「一時停止」することはできません。メソッドであるため、コンパイラは、現在の場所とローカル変数などがどのように見えるかを記憶するためのステートマシンを構築します。これは、イテレータを自分で記述するよりもはるかに簡単です。
ジェネレーターの使用方法を説明する多くの優れた答えの中に、まだ与えられていないと思う答えがあります。プログラミング言語理論の答えは次のとおりです。
Pythonの yield
ステートメントはジェネレーターを返します。 Pythonのジェネレーターは、 continuations を返す関数です(具体的にはコルーチンの一種ですが、continuationsは何が起こっているかを理解するためのより一般的なメカニズムを表します)。
プログラミング言語理論の継続は、はるかに基本的な計算ですが、推論するのが非常に難しく、実装するのも非常に難しいため、あまり使用されません。しかし、継続とは何かという考えは簡単です。それは、まだ終了していない計算の状態です。この状態では、変数の現在の値、まだ実行されていない操作などが保存されます。その後、プログラムのある時点で継続が呼び出され、プログラムの変数がその状態にリセットされ、保存された操作が実行されます。
継続は、このより一般的な形式で、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を返します(永久に繰り返します)。しかし、イテレータについて考えるときに思い浮かぶのは、おそらくプロトタイプ型のイテラブルではありません(つまり、コレクション内のxの:do_something(x)
)。この例は、ジェネレーターの能力を示しています。イテレーターがあれば、ジェネレーターはその反復の状態を保存できます。
繰り返します:継続はプログラムのスタックの状態を保存でき、ジェネレーターは繰り返しの状態を保存できます。つまり、継続はジェネレーターよりも強力ですが、ジェネレーターははるかに簡単です。言語設計者にとっては実装が簡単であり、
これは、わかりやすい言葉での例です。高レベルの人間の概念と低レベルのPythonの概念との対応を提供します。
数字のシーケンスを操作したいのですが、そのシーケンスの作成に煩わされたくないので、やりたい操作だけに集中したいです。だから、私は次のことをします:
- 私はあなたに電話し、特定の方法で生成された一連の数字が必要であることを伝え、アルゴリズムが何であるかを知らせます。
このステップは、ジェネレーター関数、つまりyield
を含む関数を定義するdef
に対応します。 - しばらくして、「OK、数字のシーケンスを教えてください」と言います。
この手順は、ジェネレーターオブジェクトを返すジェネレーター関数の呼び出しに対応しています。まだ数字を教えていないことに注意してください。紙と鉛筆をつかむだけです。 - 「次の番号を教えてください」と尋ねると、最初の番号を教えてください。その後、あなたは私が次の番号を尋ねるのを待ちます。あなたがどこにいたか、あなたがすでに言った数字、そして次の数字は何であるかを覚えておくことはあなたの仕事です。詳細は気にしません。
このステップは、ジェネレーターオブジェクトでの.next()
の呼び出しに対応しています。 - &#8230;前の手順を繰り返し、&#8230;
- 最終的に、あなたは終わりを迎えるかもしれません。数字を教えてくれません。ただ叫ぶ、「馬を抱きしめて!」私はこれで終わりです!これ以上の数字はありません!&quot;
このステップは、ジョブを終了し、StopIteration
例外を発生させるジェネレーターオブジェクトに対応しますジェネレーター関数は例外を発生させる必要はありません。関数が終了するか、return
を発行すると自動的に発生します。
これはジェネレーターが行うことです( yield
を含む関数);実行を開始し、 yield
を実行するたびに一時停止し、 .next()
の値を要求されると、最後の時点から継続します。 Pythonのイテレータプロトコルを使用した設計により、値にシーケンシャルにリクエストする方法が記述されています。
イテレータプロトコルの最も有名なユーザーは、Pythonの for
コマンドです。だから、あなたがするたびに:
for item in sequence:
sequence
が上記のようなリスト、文字列、辞書、ジェネレーターオブジェクトのいずれでもかまいません。結果は同じです。シーケンスからアイテムを1つずつ読み取ります。
def
yield
キーワードを含む関数を作成することがジェネレーターを作成する唯一の方法ではないことに注意してください。作成する最も簡単な方法です。
より正確な情報については、イテレータタイプ、< href = "http://docs.python.org/reference/simple_stmts.html#yield" rel = "noreferrer"> yieldステートメントおよびジェネレータ。
ジェネレーターを作成するために yield
を使用する理由は多くの回答で示されていますが、 yield
の用途は他にもあります。コルーチンを作成するのは非常に簡単です。これにより、2つのコードブロック間で情報を渡すことができます。 yield
を使用してジェネレーターを作成することについて既に示したすばらしい例は繰り返しません。
次のコードで yield
が何をするかを理解するために、指を使用して yield
を持つコードのサイクルをトレースできます。指が yield
に当たるたびに、 next
または send
が入力されるのを待つ必要があります。 next
が呼び出されると、 yield
&#8230;に到達するまでコードをトレースします。 yield
の右側のコードが評価され、呼び出し元に返されます&#8230;待ってます next
が再度呼び出されると、コードを介して別のループを実行します。ただし、コルーチンでは、 yield
を send
&#8230;とともに使用することもできます。呼び出し元からyield関数に値を送信します。 send
が指定されている場合、 yield
は送信された値を受け取り、それを左側に吐き出します。#8230;その後、 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()
もう一つの...がある yield
使用法と意味 (Python 3.3 以降):
yield from <expr>
から PEP 380 -- サブジェネレーターに委任するための構文:
ジェネレーターがその操作の一部を別のジェネレーターに委任するための構文が提案されています。これにより、「yield」を含むコードのセクションを取り出して別のジェネレーターに配置できます。さらに、サブジェネレーターは値を返すことができ、その値は委任ジェネレーターで使用できるようになります。
新しい構文は、あるジェネレーターが別のジェネレーターによって生成された値を再生成するときに、最適化の機会も開きます。
さらに これ (Python 3.5以降):
async def new_coroutine(data):
...
await blocking_action()
コルーチンが通常のジェネレーターと混同されるのを避けるため (今日では yield
両方で使われます)。
すべての素晴らしい答えですが、初心者には少し難しいです。
return
ステートメントを学習したと仮定します。
類推として、 return
と yield
は双子です。 return
は 'return and stop'を意味し、 'yield`は' return、but continue 'を意味します
return
でnum_listを取得してください。
def num_list(n):
for i in range(n):
return i
実行:
In [5]: num_list(3)
Out[5]: 0
参照してください、あなたはそれらのリストではなく単一の数字だけを取得します。 return
を使用すると、勝手に勝つことはできず、一度実装して終了するだけです。
- があります
yield
return
を yield
に置き換えます:
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
は計画した時間を実行します。
return
をそれらの1つを返す
として解釈し、 yield
を return all them
として解釈できます。これは iterable
と呼ばれます。
- もう1つのステップとして、
でreturn
yield
ステートメントを書き換えることができます
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
オブジェクトがあります:&lt; generator object num_list at 0x10327c990&gt;
結論として、それを理解するための隠phorとして:
-
return
とyield
は双子です -
list
とgenerator
は双子です
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)
「ジェネレーターの簡単な説明については、Beazleyの「Python:Essential Reference」の19ページを読む」を投稿するつもりでしたが、他の多くの人がすでに良い説明を投稿しています。
また、 yield
は、ジェネレーター関数での二重使用としてコルーチンで使用できることに注意してください。コードスニペットと同じ使用方法ではありませんが、(yield)
を関数の式として使用できます。呼び出し元が send()
メソッドを使用して値をメソッドに送信すると、次の(yield)
ステートメントが見つかるまでコルーチンが実行されます。
ジェネレーターとコルーチンは、データフロータイプのアプリケーションをセットアップするためのクールな方法です。関数での yield
ステートメントのその他の使用について知る価値があると思いました。
プログラミングの観点から、イテレータはサンクとして実装されています。
同時実行などのイテレータ、ジェネレータ、およびスレッドプールをサンク(匿名関数とも呼ばれます)として実装するには、ディスパッチャを持つクロージャオブジェクトに送信されたメッセージを使用し、ディスパッチャは「メッセージ」に応答します。 。
http://en.wikipedia.org/wiki/Message_passing
&quot; next &quot; &quot; iter &quot;によって作成されたクロージャーに送信されるメッセージです。呼び出します。
この計算を実装する方法はたくさんあります。突然変異を使用しましたが、現在の値と次の生成者を返すことにより、突然変異なしで簡単に実行できます。
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 ->
簡単な例を次に示します。
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
これは、 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(..)
でそれらを調べると、上記のメンタルモデルが信頼できることを確認できます。
すべての答えが示唆するように、 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"
収量はオブジェクトです
関数の return
は、単一の値を返します。
関数が膨大な値のセットを返す場合は、 yield
を使用します。
さらに重要なことは、 yield
は障壁です。
CUDA言語の障壁のように、取得するまで制御を転送しません 完了。
つまり、関数のコードを最初から yield
に達するまで実行します。次に、ループの最初の値を返します。
その後、他のすべての呼び出しは、関数に記述したループをもう一度実行し、返す値がなくなるまで次の値を返します。
(以下の私の答えは、Pythonジェネレーターの使用という観点からのみであり、ジェネレータメカニズムの基礎となる実装。これには、スタックとヒープの操作のいくつかのトリックが含まれます。)
Python関数で return
の代わりに yield
を使用すると、その関数は generator function
と呼ばれる特別なものに変わります。その関数は、 generator
型のオブジェクトを返します。 yield
キーワードは、このような関数を特別に処理するようにPythonコンパイラに通知するフラグです。何らかの値が返されると、通常の関数は終了します。しかし、コンパイラの助けを借りて、ジェネレータ関数は再開可能と考えることができます。つまり、実行コンテキストが復元され、最後の実行から実行が継続されます。 returnを明示的に呼び出して、 StopIteration
例外(イテレータプロトコルの一部でもあります)を発生させるか、関数の最後に到達するまで。 generator
に関する多くの参照を見つけましたが、この one は、機能プログラミングの観点から
消化しやすいです。
(ここで、 generator
の背後にある理論的根拠と、自分自身の理解に基づいた iterator
についてお話したいと思います。これが < em>イテレータとジェネレータの本質的な動機 。このような概念は、C#などの他の言語にも現れます。)
理解しているように、大量のデータを処理する場合、通常はまずデータをどこかに保存してから、1つずつ処理します。しかし、この<未熟なアプローチには問題があります。データ量が膨大な場合、事前に全体として保存するのは費用がかかります。 したがって、 data
自体を直接保存するのではなく、何らかの metadata
を間接的に保存しないでください。つまり、データの計算方法
。
このようなメタデータをラップする方法は2つあります。
- OOアプローチでは、メタデータを
クラスとして
ラップします。これは、イテレータプロトコル(つまり、__ next __()
および__ iter __()
メソッド)を実装する、いわゆるiterator
です。これは、よく見られるイテレーターデザインパターンです。 - 機能的なアプローチでは、メタデータを
関数として
ラップします。これは いわゆるgenerator function
。しかし、内部では、返されたgeneratorオブジェクト
は、イテレータプロトコルも実装しているため、IS-A
イテレータのままです。
どちらの方法でも、イテレータ、つまり必要なデータを提供できるオブジェクトが作成されます。オブジェクト指向のアプローチは少し複雑かもしれません。とにかく、どちらを使用するかはあなた次第です。
要約すると、 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回ループします! :(
Luckily Guidoと彼のチームは、ジェネレーターを開発するのに十分なほど寛大であったため、これを行うことができました。
def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i
各反復で、 next()
と呼ばれるジェネレーター上の関数は、停止して値を「yield」する「yield」ステートメントに到達するか、または関数。この場合、最初の呼び出しで next()
はyieldステートメントまで実行し、 'n'を生成します。次の呼び出しでincrementステートメントを実行し、 'while'に戻り、評価しますそして、trueの場合、停止して再び 'n'を生成し、while条件がfalseを返し、ジェネレーターが関数の最後にジャンプするまで、そのように継続します。
多くの人が yield
ではなく return
を使用しますが、場合によっては 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)
両方の関数は同じことをしますが、 yield
は5行ではなく3行を使用し、心配する変数が1つ少なくなります。
これはコードの結果です:
ご覧のとおり、両方の関数が同じことを行います。唯一の違いは、 return_dates()
がリストを提供し、 yield_dates()
がジェネレータを提供することです。
実際の例は、ファイルを1行ずつ読み込むようなもの、またはジェネレータを作成するだけの場合です。
yield
は、関数の戻り要素のようなものです。違いは、 yield
要素が関数をジェネレーターに変えることです。ジェネレータは、何かが「譲歩」されるまで、関数のように振る舞います。ジェネレーターは、次に呼び出されるまで停止し、開始時とまったく同じポイントから継続します。 list(generator())
を呼び出すことで、すべての「yielded」値のシーケンスを取得できます。
yield
キーワードは、返される結果を収集するだけです。 return + =
yield
を考える フィボナッチ数列を計算するための簡単な 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
の存在がPythonに通知され、ジェネレーター、つまりオンデマンドで値を生成するオブジェクトを作成するためです。
では、これらの値をどのように生成しますか?これは、組み込み関数 next
を使用して直接実行するか、値を消費する構造に間接的に供給することで実行できます。
組み込みの next()
関数を使用して、 .next
/ __ next __
を直接呼び出し、ジェネレーターに値を生成させます。
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
間接的に、 for
ループ、 list
初期化子、 tuple
初期化子に fib
を提供すると、または、値を生成/生成するオブジェクトを期待するその他のものは、「消費」します。ジェネレーターは、それ以上値を生成できなくなるまで(そして戻ります):
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&quot; text&quot;
に置き換えてください):
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