web-dev-qa-db-ja.com

「yield from」ステートメントをPython 2.7コードに変換する

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
63
vkaul11

利回りの結果を使用しない場合は、*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に新しい最適化があり、古いバージョンを使用する必要がある場合。

80
abarnert

それらを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
4
ovgolovin

この問題に出会ったばかりで、戻り値 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)
2
Cliff Hill

エミュレートする方法を見つけたと思う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で説明されているさまざまなコーナーケースのサポートがありません。これらを追加することができ、機能するようになったらエントリを編集します。

2
julkiewicz

リソースコンテキスト( 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()

複雑なロジック操作では、リソースとして登録するだけでよいことに注意してください。

0
alkalinity