鉴于Python协程:
def coroutine():
score = 0
for _ in range(3):
score = yield score + 1
我想在像这样的简单循环中使用它:
cs = coroutine()
for c in cs:
print(c)
cs.send(c + 1)
...我希望打印
1
3
5
但实际上,我在yield score + 1
行上得到了一个例外:
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
如果我手动拨打next
,我可以让它工作:
c = next(cs)
while True:
print(c)
try:
c = cs.send(c + 1)
except StopIteration:
break
但我不喜欢我需要使用try / except,因为发电机通常很优雅。
那么,有没有办法在没有明确处理StopIteration
的情况下使用这样的有限协程?我很乐意改变发电机和我迭代它的方式。
Martijn指出for
循环和我对send
的调用都推进了迭代器。很公平。那么,为什么我不能在协程循环中使用两个yield语句来解决这个问题呢?
def coroutine():
score = 0
for _ in range(3):
yield score
score = yield score + 1
cs = coroutine()
for c in cs:
print(c)
cs.send(c + 1)
如果我尝试这样做,我会在send
行上收到同样的错误。
0
None
Traceback (most recent call last):
File "../coroutine_test.py", line 10, in <module>
cs.send(c + 1)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
答案 0 :(得分:3)
是的,使用协同程序,您通常必须首先使用next()
调用来“启动”生成器;它会导致生成器函数执行代码直到第一个yield
。您的问题主要是因为您使用for
循环,但使用next()
以及,但不会发送任何内容。
您可以在协程中添加额外的yield
以捕获第一个启动步骤,并添加来自PEP 342的@consumer
装饰器(根据Python 2和3调整):
def consumer(func):
def wrapper(*args,**kw):
gen = func(*args, **kw)
next(gen)
return gen
wrapper.__name__ = func.__name__
wrapper.__dict__ = func.__dict__
wrapper.__doc__ = func.__doc__
return wrapper
@consumer
def coroutine():
score = 0
yield
for _ in range(3):
score = yield score + 1
您仍然必须使用while
循环,因为for
循环无法发送:
c = 0
while True:
try:
c = cs.send(c + 1)
except StopIteration:
break
print(c)
现在,如果您希望这与for
循环一起使用,则必须了解当您 in <时,来自next()
循环的for
调用何时进入/ em>循环。当.send()
恢复生成器时,yield
表达式返回发送的值,然后生成器从那里继续。因此,生成器函数只会再次停止下一个时间yield
出现。
所以看一下这样的循环:
for _ in range(3):
score = yield score + 1
第一次使用send
时,上述代码已经执行yield score + 1
,并且现在将返回已发送的值,并将其分配给score
。 for
循环继续并获取range(3)
中的下一个值,开始另一次迭代,然后再次执行yield score + 1
并在此时暂停。然后生成下一个迭代值。
现在,如果要将发送与普通next()
迭代相结合,可以添加额外的yield
表达式,但是那些表达式需要定位,以便您的代码是暂停在正确的位置;当您打算使用yield value
时,next()
打开None
(因为它会返回target = yield
)和generator.send()
时({1}}它会返回发送的值):
@consumer
def coroutine():
score = 0
yield # 1
for _ in range(3):
score = yield score + 1 # 2
yield # 3
当您使用带有for
循环的上述'@consumer'修饰生成器时,会发生以下情况:
@consumer
装饰者通过移动到第1点来“填充”生成器。for
循环调用生成器上的next()
,然后进入第2点,生成score + 1
值。generator.send()
调用在第2点返回暂停的生成器,将发送的值分配给score
,并将生成器前进到第3点。此返回None
作为generator.send()
结果!for
循环再次调用next()
,前进到第2点,为循环提供下一个score + 1
值。所以上面的内容直接适用于你的循环:
>>> @consumer
... def coroutine():
... score = 0
... yield # 1
... for _ in range(3):
... score = yield score + 1 # 2
... yield # 3
...
>>> cs = coroutine()
>>> for c in cs:
... print(c)
... cs.send(c + 1)
...
1
3
5
请注意,@consumer
装饰器和第一个yield
现在可以再次使用; for
循环可以单独前进到第2点:
def coroutine():
score = 0
for _ in range(3):
score = yield score + 1 # 2, for advances to here
yield # 3, send advances to here
这仍然可以继续使用你的循环:
>>> def coroutine():
... score = 0
... for _ in range(3):
... score = yield score + 1 # 2, for advances to here
... yield # 3, send advances to here
...
>>> cs = coroutine()
>>> for c in cs:
... print(c)
... cs.send(c + 1)
...
1
3
5
答案 1 :(得分:2)
我会尝试你的第二次尝试。首先,将coroutine
定义为:
def coroutine():
score = 0
for _ in range(3):
yield
score = yield score + 1
此功能将输出您原来问题中的1, 3, 5
。
现在,让我们将for
循环转换为while
循环。
# for loop
for c in cs:
print(c)
cs.send(c + 1)
# while loop
while True:
try:
c = cs.send(None)
print(c)
cs.send(c + 1)
except StopIteration:
break
现在,如果我们在while
之前添加next(cs)
循环,我们可以使用以下代码。总计:
cs = coroutine()
next(cs)
while True:
try:
c = cs.send(None)
print(c)
cs.send(c + 1)
except StopIteration:
break
# Output: 1, 3, 5
当我们尝试将其转换回for循环时,我们有相对简单的代码:
cs = coroutine()
next(cs)
for c in cs:
print(c)
cs.send(c + 1)
然后根据需要输出1, 3, 5
。问题是,在for
循环的最后一次迭代中,cs
已经用尽,但再次调用send
。那么,我们如何从发电机中获得另一个yield
?让我们添加一个......
def coroutine():
score = 0
for _ in range(3):
yield
score = yield score + 1
yield
cs = coroutine()
next(cs)
for c in cs:
print(c)
cs.send(c + 1)
# Output: 1, 3, 5
此最终示例按预期迭代,没有StopIteration
异常。
现在,如果我们退一步,这可以更好地写成:
def coroutine():
score = 0
for _ in range(3):
score = yield score + 1
yield # the only difference from your first attempt
cs = coroutine()
for c in cs:
print(c)
cs.send(c + 1)
# Output: 1, 3, 5
请注意yield
如何移动,next(cs)
已移除。