13

私自身の答えに触発されて、私はそれがどのように機能するかさえ理解していませんでした。次のことを考慮してください。

def has22(nums):
    it = iter(nums)
    return any(x == 2 == next(it) for x in it)


>>> has22([2, 1, 2])
False

StopIterationに到達すると、消費されたイテレータが進むため、 a が発生すること2next(it)期待していました。ただし、この動作はジェネレータ式のみで完全に無効になっているようです! これが発生すると、ジェネレーター式はすぐに表示されbreakます。

>>> it = iter([2, 1, 2]); any(x == 2 == next(it) for x in it)
False
>>> it = iter([2, 1, 2]); any([x == 2 == next(it) for x in it])

Traceback (most recent call last):
  File "<pyshell#114>", line 1, in <module>
    it = iter([2, 1, 2]); any([x == 2 == next(it) for x in it])
StopIteration
>>> def F(nums):
        it = iter(nums)
        for x in it:
            if x == 2 == next(it): return True


>>> F([2, 1, 2])

Traceback (most recent call last):
  File "<pyshell#117>", line 1, in <module>
    F([2, 1, 2])
  File "<pyshell#116>", line 4, in F
    if x == 2 == next(it): return True
StopIteration

これでも機能します!

>>> it=iter([2, 1, 2]); list((next(it), next(it), next(it), next(it))for x in it)
[]

私の質問は、ジェネレータ式でこの動作が有効になっているのはなぜですか?

注:同じ動作3.x

4

2 に答える 2

6

開発者は、あいまいなバグを隠す可能性があるため、これを許可するのは間違いであると判断しました. そのため、 PEP 479の受け入れは、 これがなくなることを意味します。

Python 3.5from __future__ import generator_stopでは 、Python 3.7 ではデフォルトで、質問の例はRuntimeError. numsいくつかの itertools マジックを使用して、同じ効果を得ることができます (事前に計算しないことを許可します)。

from itertools import tee, islice

def has22(nums):
    its = tee(nums, 2)
    return any(x == y == 2 for x, y in 
               zip(its[0], islice(its[1], 1, None)))

そもそもこれが機能した理由は、ジェネレーターの仕組みに関係しています。これをforループと考えることができます:

for a in b:
    # do stuff

これと(おおよそ)同等であるとして:

b = iter(b) 
while True:
    try:
        a = next(b)
    except StopIteration:
        break
    else:
        # do stuff

ここで、すべての例で2 つの for ループが入れ子になっているため (1 つはジェネレータ式で、もう 1 つはそれを使用する関数で)、外側のループがそのnext呼び出しを実行するときに内側のループが 1 回繰り返されます。内側のループの '# do stuff' が の場合はどうなりraise StopIterationますか?

>>> def foo(): raise StopIteration
>>> list(foo() for x in range(10))
[]

例外はガードされていないため、内側のループから伝播し、外側のループによってキャッチされます。新しい動作では、Python はジェネレーターから伝播しようとしている a をインターセプトし、それを含む for ループによってキャッチされないStopIterationに置き換えます。RuntimeError

これには、次のようなコードも含まれます。

def a_generator():
     yield 5
     raise StopIteration

失敗することもあり、メーリング リストのスレッドは、とにかくこれは悪い形式と見なされたという印象を与えます。これを行う適切な方法は次のとおりです。

def a_generator():
    yield 5
    return

あなたが指摘したように、リスト内包表記はすでに異なる動作をしています:

>>> [foo() for x in range(10)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
  File "<stdin>", line 1, in foo
StopIteration

これはいくぶん実装の詳細が漏れています。リスト内包表記は、同等のジェネレーター式を使用した呼び出しに変換されませんlist。明らかにそうすると、パワーが法外と見なされる大きなパフォーマンス ペナルティが発生します。

于 2013-05-29T12:48:26.647 に答える