I came across a funny behavior ofyield today that I don't really understand. Here's my code:
def a(): def b(x): print("entering b.") yield 0 if x == 0: print("calling b.") b(x + 1) print("return from b.") print("leaving b.") for x in b(0): yield xfor x in a(): print(x)That outputs:
entering b.0calling b.return from b.leaving b.What quite confuses me is that explicitly callingb(x + 1) does not callb (!), neither does Python give any error or exception.
Now, obviously the error in the code above is thatb(x + 1) should really yield the value thatb yields - so it should read something like:
for x in b(x + 1): yield xThings work then.
Still, is this something withyield I should be aware of?
- 4You might want
yield from b(x + 1)snakecharmerb– snakecharmerb2019-02-24 20:27:59 +00:00CommentedFeb 24, 2019 at 20:27 - 1Calling
bdoesn't run the body. That doesn't change just because it's a recursive call.user2357112– user23571122019-02-24 20:29:11 +00:00CommentedFeb 24, 2019 at 20:29 - When you say "calling
b(x + 1)does not callb" where are you calling it from? If it is outside ofait doesn't get called because it is out of scopeliamhawkins– liamhawkins2019-02-24 20:29:45 +00:00CommentedFeb 24, 2019 at 20:29 - 3The function callis executed; the resulting generator is simply ignored.chepner– chepner2019-02-24 20:33:40 +00:00CommentedFeb 24, 2019 at 20:33
- 1Not sure if
print("calling b.")is confusing you? And since you have noyieldfrom your recursive call - that will also not return what you probably expect it to.. as snake points out.Torxed– Torxed2019-02-24 20:34:07 +00:00CommentedFeb 24, 2019 at 20:34
2 Answers2
Theb(x + 1) is called, but not executed until yielded in the context of the calling function.
Usingyield from to yield all the values produced by that call tob() and execute the body:
def a(): def b(x): print("entering b.") yield 0 if x == 0: print("calling b.") yield from b(x + 1) print("return from b.") print("leaving b.") for x in b(0): yield xfor x in a(): print(x)4 Comments
yield from), then why "entering b." is not printed?next element.b(x+1) doesn't do anything except return a generator; you still have toiterate over that generator in order for the body to execute. That's the difference betweenyield andreturn.next is called. Often in the context of being iterated over.Theanswer you got so far is right (and I've upvoted it), but I see you're still fighting with this a bit, so let's try this variant:
def a(): def b(x): print("entering b.") yield 0 if x == 0: print("calling b.") temp = b(x + 1) print("calling b resulted in temp =", temp) print("return from b.") print("leaving b.") for x in b(0): yield xfor x in a(): print(x)Now let's run this in Python 3.x:
entering b.0calling b.calling b resulted in temp = <generator object a.<locals>.b at 0x800ac9518>return from b.leaving b.That is,temp is set to the result of callingb(x + 1), and thatresult is this<generator object ...> thing.
You then have to do somethingwith the generator object, so here is yet a third variant:
def a(): def b(x): print("entering b.") yield 0 if x == 0: print("calling b.") temp = b(x + 1) print("calling b resulted in temp =", temp) y = next(temp) print("by doing next(temp), I got", y) print("return from b.") print("leaving b.") for x in b(0): yield xfor x in a(): print(x)Running this produces:
entering b.0calling b.calling b resulted in temp = <generator object a.<locals>.b at 0x800ac9518>entering b.by doing next(temp), I got 0return from b.leaving b.Theyield from variant in the other answer basically means "keep calling temp and yielding whatever it yields, until it says it's done". Thisy = next(temp) called temp just once.
Exercise for the reader: Try the fourth variant quoted below. Try to predict, before you run it, what you'll see. Do you see what you predicted?
def a(): def b(x): print("entering b.") yield 0 if x == 0: print("calling b.") temp = b(x + 1) print("calling b resulted in temp =", temp) y = next(temp) print("by doing next(temp), I got", y) try: print("about to re-enter temp") y = next(temp) print("with the second next(temp), I got", y) except StopIteration: print("with the second next(temp), I got StopIteration") print("return from b.") else: print("b had x =", x) print("leaving b.") for x in b(0): yield xfor x in a(): print(x)Comments
Explore related questions
See similar questions with these tags.
