web-dev-qa-db-ja.com

Python:yieldとyieldの割り当て

割り当てとyield演算子を含むこのコードはどのように機能しますか?結果はかなり混乱しています。

def test1(x): 
    for i in x:
        _ = yield i 
        yield _
def test2(x): 
    for i in x:
        _ = yield i 

r1 = test1([1,2,3])
r2 = test2([1,2,3])
print list(r1)
print list(r2)

出力:

[1, None, 2, None, 3, None] 
[1, 2, 3]
19
Charlie Haley

割り当て構文(「yieldexpression」)を使用すると、ジェネレーターを基本的なコルーチンとして扱うことができます。

PEP 342 で最初に提案され、ここに文書化されています: https://docs.python.org/2/reference/expressions.html#yield-expressions

ジェネレーターで動作しているクライアントコードは、send()メソッドを使用してデータをジェネレーターに戻すことができます。そのデータには、割り当て構文を介してアクセスできます。

send()も繰り返されるため、実際にはnext()呼び出しが含まれます。

あなたの例を使用すると、これはコルーチン機能を使用するのがどのようなものになるかです:

_>>> def test1(x):
...     for i in x:
...         _ = yield i
...         yield _
...
>>> l = [1,2,3]
>>> gen_instance = test1(l)

>>> #First send has to be a None
>>> print gen_instance.send(None)
1
>>> print gen_instance.send("A")
A
>>> print gen_instance.send("B")
2
>>> print gen_instance.send("C")
C
>>> print gen_instance.send("D")
3
>>> print gen_instance.send("E")
E
>>> print gen_instance.send("F")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
_

送信されたデータをキャプチャしない各ループ反復の2番目のyieldのために、送信の一部が失われることに注意してください。

EDIT:例で生成されたNonesの説明を忘れました。

から https://docs.python.org/2/reference/expressions.html#generator.next

Next()メソッドを使用してジェネレーター関数を再開すると、現在のyield式は常にNoneと評価されます。

next()は、反復構文を使用するときに使用されます。

15
Jeremy Brown
__ = yield i 
yield _
_

まず、yieldsは、iによって参照される値です。例: _1_。次に、yield操作によって返される値であるNoneを生成します。これは、ループの各反復で実行されます。

_for i in x:
    _ = yield i
_

これは単にyieldsiによって参照される値です。例: _1_、次にループの次の反復に進み、_2_、次に_3_を生成します。

returnとは異なり、yieldキーワードは式で使用できます。

_x = return 0 # SyntaxError
x = yield 0 # perfectly fine
_

これで、インタプリタがyieldを検出すると、示された値が生成されます。ただし、そうすると、その操作は値Noneを返します。これは、mylist.append(0)またはprint('hello')が値returnNoneするのと同じです。その結果を___のような参照に割り当てると、そのNoneが保存されます。

したがって、最初のスニペットでは、オブジェクトを生成し、次にそのyield操作の「結果」(None)を保存し、次にyieldそのNoneを保存します。 2番目のスニペットでは、オブジェクトを生成し、そのyield操作の「結果」を保存しますが、その結果yieldしないので、Noneは出力には表示されません。

yieldが常にNoneを返すとは限らないことに注意してください。これは、send()を使用してジェネレーターに送信したものです。この場合は何もなかったので、Noneを取得します。 send()の詳細については、 この回答 を参照してください。

5
TigerhawkT3

TigerhawkT3の答えを拡張するために、yield操作がコードでNoneを返す理由は、list(r1)が何も送信していないためですintoジェネレーター。これを試して:

def test1(x):
    for i in x:
        _ = yield i
        yield _


r1 = test1([1, 2, 3])

for x in r1:
    print('   x', x)
    print('send', r1.send('hello!'))

出力:

   x 1
send hello!
   x 2
send hello!
   x 3
send hello!

これは、ジェネレーターに値を送信すると便利な、やや製造された例です。

def changeable_count(start=0):
    current = start
    while True:
        changed_current = yield current
        if changed_current:
            current = changed_current
        else:
            current += 1

counter = changeable_count(10)

for x in range(20):
    print(next(counter), end=' ')

print()
print()

print('Sending 51, printing return value:', counter.send(51))
print()

for x in range(20):
    print(next(counter), end=' ')

print()
print()

print('Sending 42, NOT printing return value')
print()

counter.send(42)

for x in range(20):
    print(next(counter), end=' ')

print()

出力:

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 

Sending 51, printing return value: 51

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 

Sending 42, NOT printing return value

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
3
Cyphase