Pythonでのyield
キーワードの使用は何ですか?それは何をするためのものか?
例えば、私はこのコードを理解しようとしています 1 :
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
そしてこれが呼び出し側です。
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
メソッド_get_child_candidates
が呼び出されるとどうなりますか?リストは返されますか?単一の要素?また呼ばれますか。その後の通話はいつ停止しますか。
1.このコードはJochen Schulz(jrschulz)によって書かれました。彼は距離空間のための素晴らしいPythonライブラリを作りました。これは完全なソースへのリンクです: モジュールmspace 。
yield
の機能を理解するには、generatorsが何であるかを理解する必要があります。そして、ジェネレータを理解する前に、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
"for... in...
"で使用できるものはすべて反復可能です。 lists
、strings
、ファイル...
これらのイテラブルは必要なだけ読むことができるので便利ですが、すべての値をメモリに保存します。多くの値がある場合、これは必ずしも必要なものではありません。
ジェネレーターはイテレーターで、一種の反復可能で、1回しか反復できません。ジェネレーターはすべての値をメモリに保存するわけではありません、その場で値を生成します:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
()
の代わりに[]
を使用した以外はまったく同じです。ただし、ジェネレーターは一度しか使用できないため、cannotfor i in mygenerator
を2回実行します。0を計算し、それを忘れて1を計算します、4の計算を1つずつ終了します。
yield
は、return
と同様に使用されるキーワードですが、関数がジェネレーターを返すことを除きます。
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
ここでは役に立たない例ですが、関数が巨大な値のセットを返すことを知っていると便利です。
yield
をマスターするには、関数を呼び出すときに関数本体に記述したコードが実行されないことを理解する必要があります。関数は、ジェネレーターオブジェクト、これは少し注意が必要です:-)
その後、for
がジェネレーターを使用するたびに、コードは中断したところから続行します。
今、難しい部分:
for
が関数から作成されたジェネレーターオブジェクトを初めて呼び出すと、関数のコードを最初からyield
に達するまで実行し、ループの最初の値を返します。次に、他の各呼び出しは、関数に記述したループをもう一度実行し、返される値がなくなるまで次の値を返します。
関数が実行されると、ジェネレーターは空と見なされますが、yield
にヒットしなくなります。ループが終了したか、または"if/else"
をもう満たさないことが原因である可能性があります。
ジェネレータ:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
発信者:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidates list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
このコードには、いくつかのスマートパーツが含まれています。
ループはリスト上で反復しますが、ループの反復中にリストが展開します:-)無限ループになる可能性があるため、少し危険な場合でも、これらのネストされたデータをすべて簡単に調べることができます。この場合、candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
はジェネレーターのすべての値を使い果たしますが、while
は同じジェネレーターオブジェクトを作成し続け、同じノードに適用されないため、以前のものとは異なる値を生成します。
extend()
メソッドは、反復可能なオブジェクトを期待し、その値をリストに追加するリストオブジェクトメソッドです。
通常、リストを渡します:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
ただし、コードではジェネレーターを取得します。
Pythonはメソッドの引数がリストであるかどうかを気にしないので機能します。 Pythonはイテラブルを想定しているため、文字列、リスト、タプル、ジェネレーターで動作します!これはカモタイピングと呼ばれ、Pythonがとてもクールな理由の1つです。しかし、これは別の質問です。
ここで停止するか、少し読んでジェネレーターの高度な使用法を確認してください。
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
注:Python 3の場合、useprint(corner_street_atm.__next__())
またはprint(next(corner_street_atm))
リソースへのアクセスの制御など、さまざまなことに役立ちます。
Itertoolsモジュールには、イテラブルを操作するための特別な関数が含まれています。発電機を複製したいですか? 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
ループがどのように機能するか については、この記事で詳しく説明しています。
yield
yield
ステートメントを含む関数を見つけたら、この簡単なトリックを適用して、何が起こるかを理解します。
result = []
行を挿入します。yield expr
をresult.append(expr)
に置き換えます。return result
行を挿入します。yield
ステートメントはありません!コードを読んで理解する。このトリックは、関数の背後にあるロジックのアイデアを与えるかもしれませんが、実際にyield
で起こることは、リストベースのアプローチで起こることとは大きく異なります。多くの場合、yieldアプローチはメモリ効率が非常に高く、高速です。他の場合では、元の関数が正常に機能していても、このトリックにより無限ループに陥ります。続きを読んで詳細をご覧ください...
まず、イテレータプロトコル-を書くとき
for x in mylist:
...loop body...
Pythonは次の2つの手順を実行します。
mylist
の反復子を取得します。
iter(mylist)
を呼び出す->これは、next()
メソッド(またはPython 3の__next__()
)を持つオブジェクトを返します。
[これは、ほとんどの人があなたに伝えることを忘れるステップです]
イテレータを使用してアイテムをループします。
ステップ1から返されたイテレーターでnext()
メソッドを呼び出し続けます。next()
からの戻り値がx
に割り当てられ、ループ本体が実行されます。例外StopIteration
がnext()
内から発生した場合、反復子に値がなくなったことを意味し、ループは終了します。
真実は、Pythonは、オブジェクトのコンテンツをループオーバーしたいときはいつでも、上記の2つのステップを実行します。 forループですが、otherlist.extend(mylist)
(otherlist
はPythonリスト)のようなコードにすることもできます。
ここで、mylist
はiterableです。これは、反復プロトコルを実装しているためです。ユーザー定義のクラスでは、__iter__()
メソッドを実装して、クラスのインスタンスを反復可能にすることができます。このメソッドはiteratorを返す必要があります。イテレータは、next()
メソッドを持つオブジェクトです。 __iter__()
とnext()
の両方を同じクラスに実装し、__iter__()
がself
を返すようにすることができます。これは単純な場合には機能しますが、2つのイテレーターが同じオブジェクトを同時にループさせたい場合には機能しません。
これがイテレータプロトコルです。多くのオブジェクトがこのプロトコルを実装しています。
__iter__()
を実装するユーザー定義のクラス。for
ループは、それがどの種類のオブジェクトを扱っているかを知らないことに注意してください-イテレータプロトコルに従うだけで、next()
を呼び出すときに項目ごとに取得できます。組み込みリストは項目を1つずつ返し、辞書はkeysを1つずつ返し、ファイルは行を返します1つずつ、など。そして、ジェネレーターは戻ります... yield
が入る場所です:
def f123():
yield 1
yield 2
yield 3
for item in f123():
print item
yield
ステートメントの代わりに、f123()
に3つのreturn
ステートメントがある場合、最初のステートメントのみが実行され、関数は終了します。ただし、f123()
は通常の関数ではありません。 f123()
が呼び出されると、は返されませんyieldステートメントの値を返しません!ジェネレーターオブジェクトを返します。また、関数は実際には終了せず、中断状態になります。 for
ループがジェネレーターオブジェクトをループしようとすると、関数は、前回返されたyield
の直後の次の行で中断状態から再開し、この場合、次のコード行を実行します。 yield
ステートメント、およびそれを次のアイテムとして返します。これは、関数が終了するまで発生し、その時点でジェネレーターがStopIteration
を発生させ、ループが終了します。
そのため、ジェネレーターオブジェクトはアダプターのようなものです-一端、__iter__()
およびnext()
メソッドを公開してfor
ループを維持するイテレータープロトコルを示します。ただし、もう一方の端では、次の値を取得するのに十分なだけ関数を実行し、サスペンドモードに戻します。
通常、ジェネレーターを使用せずに同じロジックを実装するコードを作成できます。 1つのオプションは、前述の一時リスト「トリック」を使用することです。それは、すべての場合に機能するわけではありません。無限ループがある場合、またはリストが非常に長い場合にメモリを非効率的に使用する可能性があります。もう1つの方法は、インスタンスメンバーの状態を保持し、next()
(またはPythonの__next__()
で次の論理ステップを実行する新しい反復可能クラスSomethingIter
を実装することです。 ) 方法。ロジックによっては、next()
メソッド内のコードが非常に複雑になり、バグが発生しやすくなる場合があります。ここで、ジェネレータはクリーンで簡単なソリューションを提供します。
このように考えてください。
イテレータは、next()メソッドを持つオブジェクトの単なる空想的な用語です。そのため、降伏関数は次のようになります。
元のバージョン:
def some_function():
for i in xrange(4):
yield i
for i in some_function():
print i
これは基本的にPythonインタプリタが上記のコードを使ってすることです:
class it:
def __init__(self):
# Start at -1 so that we get 0 when we add 1 below.
self.count = -1
# The __iter__ method will be called once by the 'for' loop.
# The rest of the magic happens on the object returned by this method.
# In this case it is the object itself.
def __iter__(self):
return self
# The next method will be called repeatedly by the 'for' loop
# until it raises StopIteration.
def next(self):
self.count += 1
if self.count < 4:
return self.count
else:
# A StopIteration exception is raised
# to signal that the iterator is done.
# This is caught implicitly by the 'for' loop.
raise StopIteration
def some_func():
return it()
for i in some_func():
print i
舞台裏で何が起こっているのかについてのより多くの洞察力のために、for
ループはこれに書き直されることができます:
iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass
それはもっと理にかなっているのでしょうか、それともあなたをもっと混乱させるのでしょうか。 :)
これは が の説明のための単純化しすぎであることに注意してください。 :)
yield
キーワードは、2つの単純な事実にまとめられています。
yield
キーワード 任意の場所 を検出した場合、その関数はreturn
ステートメントを介して返されなくなります。 代わりに、それは即時はlazy "pending list" objectを返します。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のmakeRange
のような関数range
を定義しましょう。 makeRange(n)
を呼び出すとジェネレータが返されます。
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1
>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>
ジェネレータに保留中の値をすぐに返させるには、それをlist()
に渡します(繰り返し可能にできるのと同じように)。
>>> list(makeRange(5))
[0, 1, 2, 3, 4]
上記の例は単に追加して返すリストを作成することと考えることができます。
# list-version # # generator-version
def makeRange(n): # def makeRange(n):
"""return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1"""
TO_RETURN = [] #>
i = 0 # i = 0
while i < n: # while i < n:
TO_RETURN += [i] #~ yield i
i += 1 # i += 1 ## indented
return TO_RETURN #>
>>> makeRange(5)
[0, 1, 2, 3, 4]
ただし、大きな違いが1つあります。最後のセクションを見てください。
イテラブルはリスト内包表記の最後の部分であり、すべての生成子はイタラブルであるため、それらはしばしば次のように使用されます。
# _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]
ジェネレータの感覚を良くするために、itertools
モジュールを試してみることができます(保証されている場合はchain
ではなくchain.from_iterable
を必ず使用してください)。たとえば、itertools.count()
のような無限に長い遅延リストを実装するためにジェネレータさえ使うかもしれません。独自のdef enumerate(iterable): Zip(count(), iterable)
を実装することも、whileループ内でyield
キーワードを使用して実装することもできます。
注意してください: コルーチンの実装 や非決定論的プログラミング、その他の洗練されたものなど、ジェネレータは実際にはもっと多くのことに使用できます。ただし、ここで紹介する「遅延リスト」の観点は、最も一般的な用途です。
これが「Python反復プロトコル」のしくみです。つまり、list(makeRange(5))
を実行したときに何が起こっているのでしょうか。これが私が以前に "怠惰な、増分リスト"として説明しているものです。
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
組み込み関数next()
はオブジェクト.next()
関数を呼び出すだけです。これは「反復プロトコル」の一部であり、すべての反復子で見られます。あなたは手動でnext()
関数(そして繰り返しプロトコルの他の部分)を使って手の込んだものを実装することができますが、通常は読みやすさを犠牲にしてです。
通常、ほとんどの人は次の区別を気にしないで、おそらくここで読むのをやめたいと思うでしょう。
Pythonでは、 iterable はリスト[1,2,3]
のように "for-loopの概念を理解している"オブジェクトであり、 iterator は[1,2,3].__iter__()
のような要求されたforループの特定のインスタンスです。 A generator は(関数構文による)記述方法以外はすべてのイテレータとまったく同じです。
リストから反復子を要求すると、新しい反復子が作成されます。ただし、イテレータからイテレータを要求すると(これはめったに行われません)、それ自体がコピーされます。
このように、あなたがこのようなことをするのに失敗しているというありそうもない出来事において...
> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]
...そして、ジェネレータは イテレータ であることを思い出してください。つまり、使い捨てです。再利用したい場合は、もう一度myRange(...)
を呼び出す必要があります。結果を2回使用する必要がある場合は、結果をリストに変換して変数x = list(myRange(5))
に格納してください。コピー可能な反復子Python PEP 標準の提案は延期されているので、絶対にジェネレーターを複製する必要がある人(例えば、ひどくハックなメタプログラミングをしている人)は itertools.tee
を絶対に必要とします。
yield
はreturn
と同じです - あなたが指示したものを(ジェネレータとして)返します。違いは、次にジェネレータを呼び出すときに、最後のyield
文の呼び出しから実行が開始されることです。 returnとは異なり、 イールドが発生してもスタックフレームはクリーンアップされませんが、呼び出し元に制御が戻されるため、次に関数が呼び出されたときにその状態が再開されます。
あなたのコードの場合、関数get_child_candidates
はイテレータのように振る舞うので、リストを拡張するとき、それは一度に1つの要素を新しいリストに追加します。
list.extend
は、使い果たされるまでイテレータを呼び出します。あなたが投稿したコードサンプルの場合、Tupleを返してそれをリストに追加するほうがずっと明確です。
もう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);
それは本当にいくつかの問題を単純化するのを助け、そしていくつかの事をより簡単にします。
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
は sugary の言い方です
一連のものを構築する
同じ動作:
>>> for square in square_list(4):
... print(square)
...
0
1
4
9
>>> for square in square_yield(4):
... print(square)
...
0
1
4
9
異なる動作:
収量はsingle-passです:1回だけ反復できます。関数にyieldが含まれる場合、 generator function と呼びます。そして、 iterator はそれが返すものです。これらの用語は明らかになっています。コンテナの利便性は失われますが、必要に応じて計算され、任意の長さのシリーズのパワーが得られます。
収量はlazyであり、計算を先送りします。 yieldを含む関数は、呼び出しても実際にはまったく実行されません。中断した場所を記憶する iterator object を返します。イテレータでnext()
を呼び出すたびに(これはforループで発生します)、次のyieldまで実行を数インチ進めます。 return
はStopIterationを発生させ、シリーズを終了します(これはforループの自然な終了です)。
収量は汎用性です。データをすべて一緒に保存する必要はなく、一度に1つずつ利用可能にすることができます。無限にすることができます。
>>> def squares_all_of_them():
... x = 0
... while True:
... yield x * x
... x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
... print(next(squares))
...
0
1
4
9
複数のパスが必要で、シリーズが長すぎない場合は、list()
を呼び出します:
>>> list(square_yield(4))
[0, 1, 4, 9]
両方の意味 が適用されるため、Word yield
の素晴らしい選択:
yield—生産または提供(農業など)
...シリーズの次のデータを提供します。
yield—道を譲るか放棄するか(政治権力のように)
...イテレータが進むまでCPUの実行を放棄します。
最小限の実用的な例を好む人のために、このインタラクティブな Python sessionについて黙想してください。
>>> def f():
... yield 1
... yield 2
... yield 3
...
>>> g = f()
>>> for i in g:
... print i
...
1
2
3
>>> for i in g:
... print i
...
>>> # Note that this time nothing was printed
ジェネレーターを返しています。私はPythonに特に精通しているわけではありませんが、それらに精通していれば C#のイテレーターブロック と同じようなものだと思います。
重要なアイデアは、コンパイラ/インタープリター/何でもトリックを行うことです。そのため、呼び出し元に関する限り、next()を呼び出し続けることができ、値を返し続けます-ジェネレーターメソッドが一時停止されました。明らかに、メソッドを実際に「一時停止」することはできないので、コンパイラは、現在の場所とローカル変数などがどのように見えるかを覚えておくためのステートマシンを構築します。これは、イテレータを自分で記述するよりもはるかに簡単です。
収量はあなたにジェネレータを与えます。
def get_odd_numbers(i):
return range(1, i, 2)
def yield_odd_numbers(i):
for x in range(1, i, 2):
yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5
ご覧のとおり、最初の場合、fooはリスト全体を一度にメモリに保持します。 5つの要素を持つリストにとって大したことではありませんが、500万のリストが必要な場合はどうしますか。これは巨大なメモリを消費するだけでなく、関数が呼び出される時点で構築するのにも多くの時間がかかります。後者の場合、barは単にジェネレータを提供します。ジェネレータはイテラブルです - つまりforループなどでそれを使うことができるということですが、それぞれの値は一度しかアクセスできません。すべての値も同時にメモリに格納されません。ジェネレータオブジェクトは、前回呼び出したときのループの位置を「記憶」しています。つまり、500億までの反復可能変数を使用している場合は、500億までカウントする必要はありません一度に数えて500億の数字を保存します。繰り返しますが、これはかなり人為的な例です。あなたが本当に500億まで数えたいのなら、おそらくあなたはitertoolsを使うでしょう。 :)
これはジェネレータの最も単純なユースケースです。あなたが言ったように、それは効率的な順列を書くために使うことができ、ある種のスタック変数を使う代わりにyieldを使って呼び出しスタックを通して物事を押し上げることができます。ジェネレータは特殊なツリートラバースやその他のあらゆる方法にも使用できます。
発電機の使い方を説明している多くの素晴らしい答えの中には、私がまだ与えられていないと感じる答えがあります。これがプログラミング言語理論の答えです。
Pythonのyield
ステートメントはジェネレータを返します。 Pythonのジェネレータは、を返す関数です。 続き (そして特に一種のコルーチンですが、継続は何が起こっているのかを理解するためのより一般的なメカニズムを表します)。
プログラミング言語理論における継続は、はるかに基本的な種類の計算ですが、それらを使用することは非常に困難であり、実装も非常に難しいため、使用されることはあまりありません。しかし、継続とは何かという考えは簡単です。それはまだ終わっていない計算の状態です。この状態では、変数の現在の値、まだ実行されていない操作などが保存されます。その後、プログラムの後の時点で、プログラムの変数がその状態にリセットされ、保存された操作が実行されるように、継続を呼び出すことができます。
このより一般的な形式の継続は、2つの方法で実装できます。 call/cc
の方法では、プログラムのスタックは文字通り保存され、そして継続が呼び出されるとスタックが復元されます。
継続渡しスタイル(CPS)では、継続はプログラマが明示的に管理してサブルーチンに渡す通常の関数(関数が第1クラスである言語のみ)です。このスタイルでは、プログラム状態はスタック上のどこかに存在する変数ではなく、クロージャー(およびその中にエンコードされることがある変数)によって表されます。制御フローを管理する関数は引数として継続を受け入れ(CPSのいくつかのバリエーションでは、関数は複数の継続を受け入れることができます)、単にそれらを呼び出して後で返すことによってそれらを呼び出すことによって制御フローを操作します。継続渡しスタイルの非常に単純な例は次のとおりです。
def save_file(filename):
def write_file_continuation():
write_stuff_to_file(filename)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
この(非常に単純な)例では、プログラマーは実際にファイルを継続に書き込む操作(多くの詳細を含む非常に複雑な操作になる可能性があります)を保存してから、その継続を渡します(つまり、最初の(クラスクロージャ)、他の演算子に追加処理を行い、必要に応じてそれを呼び出します。 (この設計パターンは実際のGUIプログラミングでよく使用されています。コードの行数を節約するため、またはもっと重要なことには、GUIイベントがトリガーされた後の制御フローを管理するためです。)
この記事の残りの部分では、一般性を失うことなく、継続をCPSとして概念化します。なぜなら、それは理解して読むのがはるかに容易な地獄だからです。
それでは、Pythonのジェネレータについて話しましょう。ジェネレータは継続の特定のサブタイプです。 継続は一般的に計算(つまりプログラムの呼び出しスタック)の状態を保存することができますが、 ジェネレータは_を超えた繰り返しの状態を保存することしかできません。反復子。ただし、この定義はジェネレータの特定のユースケースでは少し誤解を招きます。例えば:
def f():
while True:
yield 4
これは明らかにその動作が明確に定義された妥当なイテラブルです - ジェネレータがそれをイタレートするたびに4を返します(そしていつまでもそうなります)。しかし、イテレータを考えるときに頭に浮かぶのは、おそらくプロトタイプ型のイテラブルではありません(つまりfor x in collection: do_something(x)
)。この例はジェネレータの力を説明します:何かがイテレータであるならば、ジェネレータはその繰り返しの状態を保存することができます。
繰り返すと、継続はプログラムのスタックの状態を保存でき、ジェネレータは繰り返しの状態を保存できる。つまり、継続はジェネレータよりもはるかに強力ですが、ジェネレータははるかに簡単です。言語設計者にとっては実装が簡単で、プログラマーにとっては使いやすいでしょう(やや時間がある場合は、読んで理解してください このページの継続と/ ccの呼び出し )。
しかし、継続渡しスタイルの単純で具体的なケースとして、ジェネレータを簡単に実装(および概念化)することができます。
yield
が呼び出されるたびに、継続を返すように関数に指示します。関数が再度呼び出されると、中断したところから開始されます。したがって、疑似疑似コード(つまり、疑似コードではなくコードではない)では、ジェネレータのnext
メソッドは基本的に次のようになります。
class Generator():
def __init__(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(iterable)
def next(self):
value, next_continuation = self.next_continuation()
self.next_continuation = next_continuation
return value
ここでyield
キーワードは実際の生成関数のための実際には構文糖です。基本的には次のようなものです。
def generatorfun(iterable):
if len(iterable) == 0:
raise StopIteration
else:
return (iterable[0], lambda:generatorfun(iterable[1:]))
これは単なる疑似コードであり、Pythonでのジェネレータの実際の実装はより複雑です。しかし、何が起こっているのかを理解するための演習として、yield
キーワードを使用せずに継続渡しスタイルを使用してジェネレータオブジェクトを実装するようにしてください。
これはわかりやすい言語での例です。私は、高レベルの人間の概念と低レベルのPythonの概念との対応関係を提供します。
数字のシーケンスを操作したいのですが、そのシーケンスを作成することに煩わされたくはありません。自分がしたい操作だけに集中したいのです。それで、私は以下をします:
def
を含む関数をyield
にすることに対応する。.next()
の呼び出しに対応しています。StopIteration
例外を発生させることに対応します。 ジェネレータ関数は例外を発生させる必要はありません。関数が終了するかreturn
を発行すると自動的に発生します。これがジェネレータが行うことです(yield
を含む関数)。それは実行を開始し、それがyield
を実行するときはいつでも一時停止し、そして.next()
値を要求されるとそれはそれが最後であった点から継続します。 Pythonのイテレータプロトコルを使用した設計により、値を連続して要求する方法を説明しています。
イテレータプロトコルの最も有名なユーザーは、Pythonのfor
コマンドです。だから、あなたがするたびに:
for item in sequence:
sequence
がリスト、文字列、辞書、ジェネレータのいずれであってもかまいません object 上記のように。結果も同じです。シーケンスから項目を1つずつ読み取っていきます。
def
キーワードを含む関数をyield
iningすることがジェネレータを作成する唯一の方法ではないことに注意してください。それを作成する最も簡単な方法です。
より正確な情報については、Pythonのドキュメントの イテレータ型 、 yieldステートメント 、および ジェネレータ について読んでください。
ジェネレータを作成するのにyield
を使用する理由はたくさんありますが、yield
にはもっと用途があります。コルーチンを作成するのは非常に簡単です。これにより、2つのコードブロック間で情報を渡すことができます。 yield
を使ってジェネレータを作成することに関してすでに与えられたすばらしい例は何も繰り返しません。
以下のコードでyield
が何をするのかを理解するのを助けるために、あなたはあなたの指を使ってyield
を持っているコードを通してサイクルをたどることができます。指がyield
に当たるたびに、next
またはsend
が入力されるのを待つ必要があります。 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()
(Python 3.3以降)yield
の使い方と意味があります。
yield from <expr>
FromPEP 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
はreturn one of them
として、yield
はreturn all of them
として解釈できます。これはiterable
と呼ばれます。
yield
ステートメントをreturn
で書き直すことができるもう1つのステップ
In [15]: def num_list(n):
...: result = []
...: for i in range(n):
...: result.append(i)
...: return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]
yield
の中心です。
リストreturn
出力とオブジェクトyield
出力の違いは次のとおりです。
リストオブジェクトからは常に[0、1、2]を取得しますが、「オブジェクトyield
出力」から取得できるのは一度だけです。そのため、Out[11]: <generator object num_list at 0x10327c990>
に表示される新しい名前generator
オブジェクトを持ちます。
結論として、それを理解するための隠phorとして:
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
ステートメントの他の使い方について知ることは価値があると思いました。
プログラミングの観点からは、反復子は thunks として実装されています。
サンク(匿名関数とも呼ばれる)として、同時実行などのためにイテレータ、ジェネレータ、およびスレッドプールを実装するには、ディスパッチャを持つクロージャオブジェクトに送信されるメッセージを使用し、ディスパッチャは「メッセージ」に答えます。
http://en.wikipedia.org/wiki/Message_passing
" next "はクロージャに送信されるメッセージで、 " iter "呼び出しによって作成されます。
この計算を実装する方法はたくさんあります。私は突然変異を使いましたが、現在の値と次の野手を返すことによって、突然変異なしでそれをするのは簡単です。
これはR6RSの構造を使ったデモですが、意味はPythonのものと全く同じです。これは同じ計算モデルであり、Pythonで書き直すために必要なのは構文の変更だけです。
Welcome to Racket v6.5.0.3. -> (define gen (lambda (l) (define yield (lambda () (if (null? l) 'END (let ((v (car l))) (set! l (cdr l)) v)))) (lambda(m) (case m ('yield (yield)) ('init (lambda (data) (set! l data) 'OK)))))) -> (define stream (gen '(1 2 3))) -> (stream 'yield) 1 -> (stream 'yield) 2 -> (stream 'yield) 3 -> (stream 'yield) 'END -> ((stream 'init) '(a b)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END ->
これは簡単な例です:
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)
実行制御転送gotcha
Yieldが実行されると、実行制御はgetNextLines()からfor
ループに渡されます。したがって、getNextLines()が呼び出されるたびに、最後に一時停止されたところから実行が開始されます。
つまり、以下のコードの関数
def simpleYield():
yield "first time"
yield "second time"
yield "third time"
yield "Now some useful value {}".format(12)
for i in simpleYield():
print i
印刷します
"first time"
"second time"
"third time"
"Now some useful value 12"
(私の下の答えはPythonジェネレータを使うという観点からのみ話すもので、 ジェネレータメカニズムの基本的な実装 、これにはスタックとヒープの操作のトリックが含まれます)ではなく)
Python関数でyield
の代わりにreturn
が使用されると、その関数はgenerator function
と呼ばれる特別なものに変わります。その関数はgenerator
型のオブジェクトを返します。yield
キーワードはpythonコンパイラにそのような関数を特別に扱うよう通知するフラグです。 通常の関数は、そこから何らかの値が返されると終了します。しかし、コンパイラの助けを借りて、ジェネレータ関数 は再開可能として と考えることができます。つまり、実行コンテキストが復元され、最後の実行から実行が続行されます。明示的にreturnを呼び出すまで、これはStopIteration
例外(これもイテレータプロトコルの一部です)を発生させるか、関数の終わりに到達します。私はgenerator
についてたくさんの参照を見つけましたが、これは onefunctional programming perspective
からのものが最もダイジェスト可能です)。
(今私はgenerator
の背後にある論理的根拠と、私自身の理解に基づくiterator
についてお話したいと思います。これがイテレータとジェネレータの本質的な動機を理解するのに役立つことを願います。 C#など、他の言語でも表示されます。)
私が理解しているように、大量のデータを処理したいときは、通常、まずどこかにデータを保管してから、1つずつ処理します。しかしこの素朴なアプローチは問題が多いです。データ量が膨大な場合は、事前に全体を保存するのは費用がかかります。data
自体を直接格納するのではなく、何らかの種類のmetadata
を間接的に格納しないでください。つまり、the logic how the data is computed
です。
そのようなメタデータをラップする2つのアプローチがあります。
as a class
をラップします。これは、イテレータプロトコルを実装した、いわゆるiterator
です(つまり、__next__()
、__iter__()
メソッド)。これはまたよく見られる イテレータデザインパターン です。as a function
をラップします。これはいわゆるgenerator function
です。しかし内部的には、返されるgenerator object
はまだIS-A
iteratorであり、イテレータプロトコルも実装しているためです。どちらの方法でも、イテレータ、つまりあなたが望むデータをあなたに与えることができる何らかのオブジェクトが作成されます。 OOアプローチは少し複雑かもしれません。とにかく、どちらを使うかはあなた次第です。
収量はオブジェクトです
関数内のreturn
は単一の値を返します。
関数が巨大な値の集合を返すようにしたい場合は 、yield
を使用してください。
さらに重要なことに、yield
は barrier です。
cUDA言語の障壁のように、それはが完成するまで支配権を譲渡しません。
つまり、最初からyield
に達するまで、関数内のコードを実行します。それから、ループの最初の値を返します。
それから、他のすべての呼び出しは、関数に書いたループをもう一度実行し、返す値がなくなるまで次の値を返します。
まとめると、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
しかしこれは非効率的です
幸いなことに、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を返してジェネレータが関数の終わりにジャンプするまで、そのまま続けます。
多くの人がreturn
ではなくyield
を使用していますが、場合によってはyield
のほうが効率的で作業が簡単になります。
これはyield
が間違いなく最適な例です。
return(関数内)
import random
def return_dates():
dates = [] # With 'return' you need to create a list then return it
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
dates.append(date)
return dates
yield(関数内)
def yield_dates():
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
yield date # 'yield' makes a generator automatically which works
# in a similar way. This is much more efficient.
呼び出し関数
dates_list = return_dates()
print(dates_list)
for i in dates_list:
print(i)
dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
print(i)
両方の関数は同じことをしますが、yield
は5行ではなく3行を使用し、心配する変数が1つ少なくなります。
これはコードの結果です。
ご覧のとおり、両方の機能が同じことをしています。唯一の違いは、return_dates()
がリストを与え、そしてyield_dates()
がジェネレータを与えるということです。
実生活の例としては、ファイルを1行ずつ読み取る、または単にジェネレータを作成する場合などがあります。
yield
は関数のreturn要素のようなものです。違いは、yield
要素が関数をジェネレータに変えることです。何かが「生成される」まで、ジェネレータは関数のように振舞います。ジェネレータは次に呼び出されるまで停止し、開始時とまったく同じ場所から続行します。 list(generator())
を呼び出すことで、すべての「得られた」値のシーケンスを1つにまとめることができます。
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
の存在が、 generator 、つまり要求に応じて値を生成するオブジェクトを作成することをPythonに通知したためです。
それで、どのようにこれらの値を生成しますか?これは組み込み関数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
呼び出しを使用しましょう(Python 2の場合はprint "text"
に置き換えます)。
def yielder(value):
""" This is an infinite generator. Only use next on it """
while 1:
print("I'm going to generate the value for you")
print("Then I'll pause for a while")
yield value
print("Let's go through it again.")
それでは、REPLを入力してください。
>>> gen = yielder("Hello, yield!")
値を生成するためのコマンドを待っているジェネレータオブジェクトがあります。 next
を使用して、何が表示されるのかを確認してください。
>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
引用符で囲まれていない結果は印刷されたものです。引用された結果はyield
から返されるものです。もう一度next
を呼び出します。
>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
ジェネレータはyield value
で一時停止したことを記憶し、そこから再開します。次のメッセージが表示され、一時停止するyield
ステートメントの検索が再度実行されました(while
ループのため)。
簡単に説明されている簡単な例:yield
def f123():
for _ in range(4):
yield 1
yield 2
for i in f123():
print i
出力は以下のとおりです。
1 2 1 2 1 2 1 2
さらに別のTL、DR
リストのイテレータ:next()
はリストの次の要素を返します
Iterator generator:next()
はその場で次の要素を計算します(コードを実行します)
next
を呼び出すことで、外部から手動で制御フローを実行する方法としてyield/generatorを見ることができますが、フローは複雑になります。
注:ジェネレータはNOT通常の関数です。ローカル変数(スタック)のように以前の状態を覚えています。詳しい説明は他の答えや記事を見てください。ジェネレータは1回反復のみです。 yield
がなくてもできますが、それほどいいものではないので、「とてもいい」言語の糖と見なすことができます。
yield はreturnに似ています。違いは:
yield 関数を反復可能にします(次の例ではprimes(n = 1)
関数が反復可能になります)。
これが本質的に意味するのは、次にその関数が呼び出されたときに、それが離れた場所(yield expression
の行の後)から続行されることです。
def isprime(n):
if n == 1:
return False
for x in range(2, n):
if n % x == 0:
return False
else:
return True
def primes(n = 1):
while(True):
if isprime(n): yield n
n += 1
for n in primes():
if n > 100: break
print(n)
上記の例でisprime(n)
が真ならば素数を返します。次の繰り返しでは次の行から続く
n += 1
ここでの答えはすべて素晴らしいです。しかし、それらのうちの1つ(最も投票されたもの)だけが あなたのコードがどのように機能するのか - に関係しています 。他のものは一般に ジェネレータ 、そしてそれらがどのように働くかに関係しています。
だから私は発電機が何であるか、またはどのような利回りが何をするのか繰り返すつもりはない。これらは既存の優れた答えでカバーされていると思います。しかし、あなたと似たようなコードを理解しようとして数時間を費やした後、私はそれがどのように機能するかについてそれを分解します。
あなたのコードは二分木構造をたどります。この木を例にとりましょう:
5
/ \
3 6
/ \ \
1 4 8
そして、二分探索木探索のもう一つのより簡単な実装は:
class Node(object):
..
def __iter__(self):
if self.has_left_child():
for child in self.left:
yield child
yield self.val
if self.has_right_child():
for child in self.right:
yield child
実行コードはTree
オブジェクトにあります。これは__iter__
を次のように実装します。
def __iter__(self):
class EmptyIter():
def next(self):
raise StopIteration
if self.root:
return self.root.__iter__()
return EmptyIter()
while candidates
ステートメントはfor element in tree
に置き換えることができます。 Pythonはこれをに翻訳します
it = iter(TreeObj) # returns iter(self.root) which calls self.root.__iter__()
for element in it:
.. process element ..
Node.__iter__
関数はジェネレータなので、コード その中の は反復ごとに実行されます。そのため、実行は次のようになります。
for
反復します(最初の反復子オブジェクトはit1とします)。for
が実行されます。 for child in self.left
は、Nodeオブジェクトそのものであるself.left
から 新しいイテレータ を作成します(it2)iterator
が作成されます(it3)it3
には残っている子がいないので続けます、そしてyield self.value
next(it3)
の呼び出しでは、StopIteration
が発生し、正しい子がないため存在します(降伏することなく関数の最後まで到達します)。it1
とit2
はまだアクティブです - それらは使い果たされず、next(it2)
を呼び出すことは_を引き上げないで値をもたらすでしょうStopIteration
it2
コンテキストに戻り、yield child
ステートメントの直後に停止したところからnext(it2)
を呼び出します。もう子が残っていないので、続けてself.val
を返します。ここでのキャッチは、すべての反復 がサブ反復子 を作成してツリーをトラバースし、現在の反復子の状態を保持することです。それが終わりに達するとそれはスタックを逆行し、値は正しい順序で返されます(最小の降伏値が最初になります)。
あなたのコード例は別のテクニックで似たようなことをしました:それはすべての子供のために 1要素のリスト を埋め、それから次の繰り返しでそれをポップして現在のオブジェクトで関数コードを実行します(したがってself
).
これがこの伝説的なトピックに少し貢献したことを願っています。私はそれを理解するためにこのプロセスを描くために数時間を費やしました。
つまり、 yield の使用法は、 generator を返す点を除いて、キーワード return に似ています。
generator objectは、 once だけを横断します。
yield には2つの利点があります。
Pythonでは、generators
(特別なタイプのiterators
)が一連の値を生成するために使用され、yield
キーワードはジェネレータ関数のreturn
キーワードと同じです。
yield
キーワードがするもう一つの魅力的なことはジェネレータ関数のstate
を保存することです 。
そのため、number
が生成されるたびにgenerator
を異なる値に設定できます。
これがインスタンスです:
def getPrimes(number):
while True:
if isPrime(number):
number = yield number # a miracle occurs here
number += 1
def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
print(primeGenerator.send(base ** power))
>>> def create_generator():
... my_list = range(3)
... for i in my_list:
... yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
... print(i)
0
1
4
要するにを見ると、(実行後にループが停止するreturn
とは異なり)オブジェクトまたは変数が送信された後でも、ループは停止せず機能し続けます。
例えはここでアイデアを把握するのを助けるかもしれません:
あなたが1日に何千もの電球を生成することができる驚くべき機械を作成したと想像してください。本機は、これらの電球を固有のシリアル番号の付いた箱に入れます。これらすべての電球を同時に保管するのに十分なスペースがありません(つまり、保管上の制限から機械の速度に追いつくことができません)。したがって、この電球をオンデマンドで生成するように調整します。
Pythonジェネレータはこの概念とそれほど違いはありません。
ボックスのためにユニークなシリアル番号を生成する関数x
があると想像してください。明らかに、あなたは関数によって生成された非常に多数のそのようなバーコードを持つことができます。より賢い、そしてスペース効率の良い選択肢は、それらのシリアル番号をオンデマンドで生成することです。
機械のコード:
def barcode_generator():
serial_number = 10000 # Initial barcode
while True:
yield serial_number
serial_number += 1
barcode = barcode_generator()
while True:
number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
print(barcodes)
# function_to_create_the_next_batch_of_lightbulbs(barcodes)
produce_more = input("Produce more? [Y/n]: ")
if produce_more == "n":
break
ご覧のとおり、次の固有のシリアル番号を毎回生成するための自己完結型の「関数」があります。この関数はジェネレータを返します!ご覧のとおり、新しいシリアル番号が必要になるたびに関数を呼び出すわけではありませんが、次のシリアル番号を取得するためにジェネレータを指定してnext()
を使用しています。
出力:
How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
yield
はpythonで使用できるジェネレータの一種です。
これは、Yieldが実際に何をしているのかを見るためのリンクです。 ジェネレータとYieldキーワード - Python Central(PC)
yield
もreturn
と同じように動作しますが、return
とは異なる方法でyield
を説明するリンクがあっても、理解できない場合もう一人はそんなによくない。 歩留まりスキルを向上させる - jeffknupp
簡単に言うと、 'yield'は値を 'return'するのと似ていますが、Generatorで機能します。
単純yieldでは、値ではなくジェネレータオブジェクトを返します。
以下の簡単な例が役立ちます!
def sim_generator():
for i in range(3):
yield(i)
obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)
上記のコードは0、1、2を返します。
あるいは短い
for val in sim_generator():
print(val)
0、1、2を返す
お役に立てれば
簡単なジェネレータ関数
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
yield文は関数がすべての状態を保存して一時停止し、その後の呼び出しでそこから続行します。
yield
は何かを生み出します。誰かがあなたに5カップケーキを作るように頼むようなものです。あなたが少なくとも1つのカップケーキを使い終わったら、あなたが他のケーキを作る間、それを食べるように彼らに与えることができます。
In [4]: def make_cake(numbers):
...: for i in range(numbers):
...: yield 'Cake {}'.format(i)
...:
In [5]: factory = make_cake(5)
ここではfactory
はgeneratorと呼ばれ、あなたがケーキを作ります。 make_function
を呼び出すと、その関数を実行する代わりにジェネレータを取得します。 yield
キーワードが関数内に存在する場合、それがジェネレータになるからです。
In [7]: next(factory)
Out[7]: 'Cake 0'
In [8]: next(factory)
Out[8]: 'Cake 1'
In [9]: next(factory)
Out[9]: 'Cake 2'
In [10]: next(factory)
Out[10]: 'Cake 3'
In [11]: next(factory)
Out[11]: 'Cake 4'
彼らはすべてのケーキを消費しましたが、彼らは再び1つを求めます。
In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)
StopIteration:
そして、彼らはもっと尋ねるのをやめるように言われています。それで、あなたが発電機を消費したら、あなたはそれで終わりです。もっとケーキが欲しいなら、もう一度make_cake
を呼ぶ必要があります。カップケーキを注文するようなものです。
In [13]: factory = make_cake(3)
In [14]: for cake in factory:
...: print(cake)
...:
Cake 0
Cake 1
Cake 2
上記のようなジェネレータを使ってforループを使うこともできます。
もう1つの例:あなたがそれを求めるときはいつでもあなたがランダムなパスワードが欲しいと言うことができます。
In [22]: import random
In [23]: import string
In [24]: def random_password_generator():
...: while True:
...: yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
...:
In [25]: rpg = random_password_generator()
In [26]: for i in range(3):
...: print(next(rpg))
...:
FXpUBhhH
DdUDHoHn
dvtebEqG
In [27]: next(rpg)
Out[27]: 'mJbYRMNo'
ここでrpg
はジェネレータです。これは無限のランダムパスワードを生成することができます。そのため、要素数が有限のlistとは異なり、シーケンスの長さがわからない場合にもジェネレータが有用であると言えます。