问题
I'm curious as to what's happening here. Can someone who knows generators and coroutines well explain this code.
def b():
for i in range(5):
yield i
x = (yield)
print(x)
def a():
g = b()
next(g)
for i in range(4):
g.send(5)
print(next(g))
a()
output
None
1
None
2
None
3
None
4
but when I switch around lines 3 and 4: the lines yield i
and x = (yield)
, I get the following.
5
None
5
None
5
None
5
None
I suspect the problem might me from trying to use the yield statement to both receive and send values in the same function. Is this not possible in Python?
I have successfully written a couple of programs that use coroutines, so I am familiar with the way they work, but I am confused as to the way this snippet of code is behaving. Any insights into this would be appreciated.
Thanks
Edit: Thanks BrenBarn and unutbu for your answers. What's happening here makes more sense when you expand the problem out as such.
def b():
for i in range(5):
yield i
x = yield None
def a():
g = b()
print('* got', g.send(None) )
for i in range(4):
print('+ got', g.send(5) )
print('- got', g.send(None))
a()
回答1:
I don't quite get what you're asking, but basically: when you use send
, it causes the most-recently-reached yield expression in the generator to evaluate to the value you send. Note also that send
advances the generator to the next yield
. One thing that may be confusing you is that you are printing the value of x
inside the generator, and you are printing the value of next(g)
inside b
, but the generator is also yielding values at g.send(5)
, and you aren't printing those.
In your first case, your first send
causes the yield i
statement to evaluate to 5 inside b
, but you don't use this value inside b
(you don't assign yield i
to anything), so it does nothing. Also, when you do send(5)
, the generator is yielding None (from the x = (yield)
line), but you don't print it so you don't know this. Then you advance the generator again with next(g)
. The most-recently-reached yield is the x = yield
, but next(g)
passes no value, so x
gets set to None.
In the second case, the parity of the calls is reversed. Now your first send
does send to the x = yield
line, so x
gets set to 5. This send
also yields the loop value in b
, but you ignore this value in a
and don't print it. You then print next(g)
, which is None. On each subsequent send, b
prints the value of x
, which is always 5 because that's what you always send, and then a
prints the next yielded value, which is always None (because that's what x = yield
yields).
I don't quite get what you mean by "using the yield statement to both receive and send values in the same function". You can certainly do this, but you have to realize that: a) a value (None) is still sent even when you call next(g)
; and b) a value is still yielded when you call g.send(5)
.
回答2:
Using traceit
to step through the program line-by-line:
import sys
import linecache
class SetTrace(object):
'''
with SetTrace(monitor):
...
'''
def __init__(self, func):
self.func = func
def __enter__(self):
sys.settrace(self.func)
return self
def passit(self, frame, event, arg):
return self.passit
def __exit__(self, ext_type, exc_value, traceback):
sys.settrace(self.passit)
def traceit(frame, event, arg):
'''
http://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html
'''
if event == "line":
lineno = frame.f_lineno
filename = frame.f_globals["__file__"]
if (filename.endswith(".pyc") or
filename.endswith(".pyo")):
filename = filename[:-1]
name = frame.f_globals["__name__"]
line = linecache.getline(filename, lineno)
print("%s # %s:%s" % (line.rstrip(), name, lineno, ))
return traceit
def b():
for i in range(5):
yield i
x = (yield)
print(x)
def a():
g = b()
next(g)
for i in range(4):
g.send(5)
print(next(g))
with SetTrace(traceit):
a()
we obtain
g = b() # __main__:44
next(g) # __main__:45 # runs b until you get to a yield
for i in range(5): # __main__:38
yield i # __main__:39 # stop before the yield; resume a
^
for i in range(4): # __main__:46
g.send(5) # __main__:47 # resume b; (yield i) expression evals to 5 then thrown away
x = (yield) # __main__:40 # stop before yield; resume a
^
print(next(g)) # __main__:48 # next(g) called; resume b; print not called yet
print(x) # __main__:41 # next(g) causes (yield) to evaluate to None
None
for i in range(5): # __main__:38
yield i # __main__:39 # yield 1; resume a; `print(next(g))` prints 1
1
for i in range(4): # __main__:46
g.send(5) # __main__:47 # resume b; (yield i) expression evals to 5 then thrown away
The comments on the right-hand side (above) explain why Python prints None
then 1
. If you get that far, I think it is clear why you get None
, 2
, etc. -- it's the same story all over again with different values for i
.
The other scenario, where x = (yield)
and yield i
are reverse can be analyzed similarly.
来源:https://stackoverflow.com/questions/13115135/python-i-dont-understand-whats-happening-with-this-generator