例の関数で終了する理由:
def func(iterable):
while True:
val = next(iterable)
yield val
しかし、yieldステートメント関数をオフにするとStopIteration例外が発生しますか?
EDIT:皆さんを誤解させてすみません。ジェネレーターとは何か、そしてその使用方法を知っています。もちろん、関数が終了すると言ったとき、私は関数の熱心な評価を意味しませんでした。私は関数を使用してジェネレーターを生成するとき、それを暗示しました:
gen = func(iterable)
funcの場合は動作し、同じジェネレーターを返しますが、func2の場合:
def func2(iterable):
while True:
val = next(iterable)
Nonereturnまたは無限ループの代わりにStopIterationを発生させます。
もっと具体的にさせてください。関数teeinitertoolsは次と同等です:
def tee(iterable, n=2):
it = iter(iterable)
deques = [collections.deque() for i in range(n)]
def gen(mydeque):
while True:
if not mydeque: # when the local deque is empty
newval = next(it) # fetch a new value and
for d in deques: # load it to all the deques
d.append(newval)
yield mydeque.popleft()
return Tuple(gen(d) for d in deques)
実際、ネストされた関数genにはbreakステートメントのない無限ループがあるため、いくつかの魔法があります。 gen関数は、itにアイテムがない場合、StopIteration例外により終了します。ただし、例外は発生せずに正しく終了します。つまり、ループを停止します。 だから質問はです:StopIterationはどこで処理されますか?
StopIteration
が_itertools.tee
_内で作成されたgen
ジェネレーターのどこでキャッチされるかという質問に答えるために、そうではありません。 tee
の結果のコンシューマーは、例外を繰り返し処理するときにキャッチします。
まず、ジェネレーター関数(yield
ステートメントを含む任意の関数)は通常の関数とは根本的に異なることに注意することが重要です。関数の呼び出し時に関数のコードを実行する代わりに、関数を呼び出すときにgenerator
オブジェクトを取得します。ジェネレーターを反復処理する場合にのみ、コードを実行します。
ジェネレーター関数は、StopIteration
を発生させずに反復を終了することはありません(代わりに他の例外を発生させない限り)。 StopIteration
は、ジェネレーターから実行されたというシグナルであり、オプションではありません。 return
ステートメントまたはジェネレーター関数のコードの最後に何も上げることなく到達すると、PythonはStopIteration
を上げます!
これは、他に何も返さずに最後に達した場合にNone
を返す通常の関数とは異なります。上で説明したように、ジェネレーターが動作するさまざまな方法と関連しています。
StopIteration
がどのように発生するかを簡単に確認できるジェネレータ関数の例を次に示します。
_def simple_generator():
yield "foo"
yield "bar"
# StopIteration will be raised here automatically
_
消費すると次のようになります。
_>>> g = simple_generator()
>>> next(g)
'foo'
>>> next(g)
'bar'
>>> next(g)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(g)
StopIteration
_
_simple_generator
_を呼び出すと、常にすぐにgenerator
オブジェクトが返されます(関数内のコードを実行せずに)。ジェネレーターオブジェクトでnext
を呼び出すたびに、次のyield
ステートメントまでコードが実行され、生成された値が返されます。取得する必要がない場合は、StopIteration
が発生します。
現在、通常、StopIteration
例外は表示されません。これは、通常、for
ループ内でジェネレーターを使用するためです。 for
ステートメントは、next
が発生するまで、自動的にStopIteration
を繰り返し呼び出します。 StopIteration
例外をキャッチして抑制しますので、try
/except
ブロックをいじる必要はありません。
for item in iterable: do_suff(item)
のようなfor
ループは、このwhile
ループとほぼ同じです(唯一の違いは、実際のfor
は一時変数を必要としないことです)イテレータを保持するため)
_iterator = iter(iterable)
try:
while True:
item = next(iterator)
do_stuff(item)
except StopIteration:
pass
finally:
del iterator
_
上に示したgen
ジェネレーター関数は1つの例外です。反復処理が完了したという独自のシグナルであるため、消費する反復子によって生成されたStopIteration
例外を使用します。つまり、StopIteration
をキャッチしてからループを抜けるのではなく、例外をキャッチしないようにします(おそらく、より高いレベルのコードでキャッチされます)。
主な質問とは無関係に、もう1つ指摘したいことがあります。コードでは、next
という変数でiterable
を呼び出しています。取得するオブジェクトの種類のドキュメントとしてその名前を使用する場合、これは必ずしも安全ではありません。
next
はiterator
(またはiterable
)プロトコルではなく、container
プロトコルの一部です。一部の種類の反復可能オブジェクト(ファイルやジェネレーターなど、独自の反復子である)で機能する場合もありますが、タプルやリストなど、その他の反復可能オブジェクトでは失敗します。より適切なアプローチは、iter
値でiterable
を呼び出し、受け取ったイテレーターでnext
を呼び出すことです。 (または、適切なタイミングでfor
とiter
の両方を呼び出すnext
ループを使用してください!)
編集:関連する質問のGoogle検索で自分の答えを見つけましたが、上記の答えが将来的に完全に真実ではないことを指摘するために更新するつもりだったPythonバージョン。 PEP 479 は、StopIteration
がジェネレーター関数からキャッチされないようにすることをエラーにしています。その場合、Pythonは代わりにRuntimeError
例外。
これは、itertools
を使用してジェネレーター関数から抜け出すStopIteration
の例のようなコードを変更する必要があることを意味します。通常、try
/except
を使用して例外をキャッチし、return
を実行する必要があります。
これは後方互換性のない変更であるため、徐々に段階的に導入されています。 In Python 3.5、すべてのコードはデフォルトで以前と同様に動作しますが、_from __future__ import generator_stop
_を使用して新しい動作を取得できます。InPython 3.6、コードは引き続き機能しますが、警告が表示されます。Python 3.7では、新しい動作が常に適用されます。
関数にyield
が含まれる場合、呼び出しても実際には何も実行されず、ジェネレーターオブジェクトが作成されるだけです。このオブジェクトを反復処理するだけでコードが実行されます。だから私の推測では、単に関数を呼び出しているだけです。つまり、関数はStopIteration
を発生させません。これは、実行されないため
あなたの関数と反復可能なものを考えます:
_def func(iterable):
while True:
val = next(iterable)
yield val
iterable = iter([1, 2, 3])
_
これは間違った呼び出し方法です:
_func(iterable)
_
これは正しい方法です:
_for item in func(iterable):
# do something with item
_
ジェネレーターを変数に保存し、その上でnext()
を呼び出す(または他の方法で反復する)こともできます。
_gen = func(iterable)
print(next(gen)) # prints 1
print(next(gen)) # prints 2
print(next(gen)) # prints 3
print(next(gen)) # StopIteration
_
ところで、関数を記述するより良い方法は次のとおりです。
_def func(iterable):
for item in iterable:
yield item
_
またはPython 3.3以降:
_def func(iterable):
yield from iter(iterable)
_
もちろん、実際の発電機がそれほど些細なことはめったにありません。 :-)
yield
がなければ、iterable
で何もすることを止めずにval
全体を反復処理します。 while
ループはStopIteration
例外をキャッチしません。同等のfor
ループは次のようになります。
def func(iterable):
for val in iterable:
pass
StopIteration
をキャッチし、単純にループを終了して関数から戻ります。
明示的に例外をキャッチできます:
def func(iterable):
while True:
try:
val = next(iterable)
except StopIteration:
break
yield
はStopIteration
をキャッチしません。 yield
が関数に対して行うことは、それが通常の関数ではなくジェネレーター関数になることです。したがって、関数呼び出しから返されるオブジェクトは、反復可能なオブジェクトです(next
関数(forループによって暗黙的に呼び出される)を使用して次の値を計算すると、次の値が計算されます)。 yield
ステートメントを省略すると、pythonはwhile
ループ全体をすぐに実行し、イテレート可能ファイルを使い果たします(有限の場合) StopIteration
を呼び出したときにすぐに呼び出します。
考慮してください:
x = func(x for x in [])
next(x) #raises StopIteration
for
ループが例外をキャッチします-これは、与えられたイテレート可能オブジェクトでnext
の呼び出しをいつ停止するかを知る方法です。