Python 3.2に以下のコードがあり、Python 2.7で実行したかった。両方のバージョンにmissing_elements
のコードを入れた。基本的に、yield from
関数の上半分と下半分に以下のような2つのmissing_element
呼び出しがある場合、どうなりますか?2つの半分(上と下)からのエントリはありますか? yield from
を呼び出して親再帰関数が両方の半分を一緒に使用するように、1つのリストで互いに追加されますか?
def missing_elements(L, start, end): # Python 3.2
if end - start <= 1:
if L[end] - L[start] > 1:
yield from range(L[start] + 1, L[end])
return
index = start + (end - start) // 2
# is the lower half consecutive?
consecutive_low = L[index] == L[start] + (index - start)
if not consecutive_low:
yield from missing_elements(L, start, index)
# is the upper part consecutive?
consecutive_high = L[index] == L[end] - (end - index)
if not consecutive_high:
yield from missing_elements(L, index, end)
def main():
L = [10, 11, 13, 14, 15, 16, 17, 18, 20]
print(list(missing_elements(L, 0, len(L)-1)))
L = range(10, 21)
print(list(missing_elements(L, 0, len(L)-1)))
def missing_elements(L, start, end): # Python 2.7
return_list = []
if end - start <= 1:
if L[end] - L[start] > 1:
return range(L[start] + 1, L[end])
index = start + (end - start) // 2
# is the lower half consecutive?
consecutive_low = L[index] == L[start] + (index - start)
if not consecutive_low:
return_list.append(missing_elements(L, start, index))
# is the upper part consecutive?
consecutive_high = L[index] == L[end] - (end - index)
if not consecutive_high:
return_list.append(missing_elements(L, index, end))
return return_list
利回りの結果を使用しない場合は、*alwaysこれを有効にすることができます:
yield from foo
…これに:
for bar in foo:
yield bar
パフォーマンスコストが発生する可能性がありますが**、意味上の違いはありません。
2つの半分(上と下)からのエントリが1つのリストで互いに追加されているため、親再帰はyield from呼び出しで両方の半分を一緒に使用しますか?
いや!イテレータとジェネレータのポイントは、実際のリストを構築してそれらを一緒に追加しないことです。
しかし、effectは似ています:あなたはただ一つから譲り、それから別のものから譲ります。
上半分と下半分を「遅延リスト」と考えると、はい、これをより大きな「遅延リスト」を作成する「遅延アペンド」と考えることができます。そして、親関数の結果に対してlist
を呼び出すと、もちろんwillは実際のlist
を取得します。 yield from …
の代わりにyield list(…)
を実行した場合に取得する2つのリストを一緒に追加します。
しかし、私はそれを他の方法で考える方が簡単だと思います:それが行うことは、for
ループが行うこととまったく同じです。
2つのイテレータを変数に保存し、itertools.chain(upper, lower)
をループすると、最初のループをループしてから2番目のループをループするのと同じになりますか?ここに違いはありません。実際、次のようにchain
を実装できます。
for arg in *args:
yield from arg
* で説明されているように、ジェネレーターが呼び出し元に渡す値ではなく、ジェネレーター(send
メソッドを使用して呼び出し元から取得される)内のyield式自体の値PEP 342 。あなたの例ではこれらを使用していません。そして、私はあなたがあなたの本当のコードにいないことを賭けて喜んでいます。しかし、コルーチン形式のコードは、多くの場合yield from
式の値を使用します。例については、 PEP 3156 を参照してください。このようなコードは通常、Python 3.3ジェネレーター、特に、StopIteration.value
を導入した同じ PEP 380 の新しいyield from
—しかし、そうでない場合は、PEPを使用して完全に恐ろしい乱雑な同等物を表示することができ、もちろん、気にしない部分を削減することができます。式の値、上の2行に切り詰めます。
**巨大なものではなく、Python 3.3を使用するか、コードを完全に再構築する以外に、あなたができることは何もありません。リスト内包表記をPython 1.5ループ、またはバージョンXYに新しい最適化があり、古いバージョンを使用する必要がある場合。
それらをforループに置き換えます。
yield from range(L[start] + 1, L[end])
==>
for i in range(L[start] + 1, L[end]):
yield i
要素についても同じ:
yield from missing_elements(L, index, end)
==>
for el in missing_elements(L, index, end):
yield el
この問題に出会ったばかりで、戻り値 of yield from
:
result = yield from other_gen()
これは単純なfor
ループとして表すことはできませんが、これで再現できます。
_iter = iter(other_gen())
try:
while True: #broken by StopIteration
yield next(_iter)
except StopIteration as e:
if e.args:
result = e.args[0]
else:
result = None
うまくいけば、これは同じ問題に出くわした人々を助けるでしょう。 :)
pep-380 の定義を使用して、Python 2構文バージョン:
ステートメント:
RESULT = yield from EXPR
意味的には次と同等です:
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
ジェネレーターでは、次のステートメント:
return value
意味的に等価です
raise StopIteration(value)
ただし、現在のように、返されるジェネレーター内のexcept
句で例外をキャッチすることはできません。
StopIteration例外は、このように定義されているかのように動作します。
class StopIteration(Exception):
def __init__(self, *args):
if len(args) > 0:
self.value = args[0]
else:
self.value = None
Exception.__init__(self, *args)
エミュレートする方法を見つけたと思うPython 3.x yield from
コンストラクトPython 2.x.これは効率的ではなく、少しハックですが、ここにあります:
import types
def inline_generators(fn):
def inline(value):
if isinstance(value, InlineGenerator):
for x in value.wrapped:
for y in inline(x):
yield y
else:
yield value
def wrapped(*args, **kwargs):
result = fn(*args, **kwargs)
if isinstance(result, types.GeneratorType):
result = inline(_from(result))
return result
return wrapped
class InlineGenerator(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def _from(value):
assert isinstance(value, types.GeneratorType)
return InlineGenerator(value)
使用法:
@inline_generators
def outer(x):
def inner_inner(x):
for x in range(1, x + 1):
yield x
def inner(x):
for x in range(1, x + 1):
yield _from(inner_inner(x))
for x in range(1, x + 1):
yield _from(inner(x))
for x in outer(3):
print x,
出力を生成します。
1 1 1 2 1 1 2 1 2 3
たぶん誰かがこれを役に立つと思うでしょう。
既知の問題: send()およびPEP 380で説明されているさまざまなコーナーケースのサポートがありません。これらを追加することができ、機能するようになったらエントリを編集します。
リソースコンテキスト( python-resources モジュールを使用)を使用して、Python 2.7。とにかくリソースコンテキスト。
Python 3.3の場合、次のようになります。
@resources.register_func
def get_a_thing(type_of_thing):
if type_of_thing is "A":
yield from complicated_logic_for_handling_a()
else:
yield from complicated_logic_for_handling_b()
def complicated_logic_for_handling_a():
a = expensive_setup_for_a()
yield a
expensive_tear_down_for_a()
def complicated_logic_for_handling_b():
b = expensive_setup_for_b()
yield b
expensive_tear_down_for_b()
Python 2.7では次のようになります。
@resources.register_func
def get_a_thing(type_of_thing):
if type_of_thing is "A":
with resources.complicated_logic_for_handling_a_ctx() as a:
yield a
else:
with resources.complicated_logic_for_handling_b_ctx() as b:
yield b
@resources.register_func
def complicated_logic_for_handling_a():
a = expensive_setup_for_a()
yield a
expensive_tear_down_for_a()
@resources.register_func
def complicated_logic_for_handling_b():
b = expensive_setup_for_b()
yield b
expensive_tear_down_for_b()
複雑なロジック操作では、リソースとして登録するだけでよいことに注意してください。