Whenever you call the method as a generator (e.g. for x in get_reverse_iterator()), python starts executing that method line by line. Whenever it hits a yield, it stops cold and returns that. When it gets asked for a next() value in the next iteration of the for loop, it continues to execute.
This looks like a fairly straightforward linked-list-traversal idiom, where each element of the list contains data that is itself a list (or some other iterable value, like a string):
list[0].data = [1, 2, 3, 4] list[1].data = [5, 6, 7, 8] ... list[9].data = [37, 38, 39, 40]
So what the code is doing here is printing those sub-lists from the back of the main list to the front of the main list. The output should look something like this:
37 38 39 40 33 34 35 36 ... 5 6 7 8 [1, 2, 3, 4]
which becomes evident when you look at how the code executes. I'll rewrite it in words:
func get_reverse_iterator(head) { if head isn't the last element of the list, then call this function on the next element of the list (head.next) for every element in the return value of that, yield that element yield this element's data
The 'base case' is the last element of the list, which doesn't have a .next. So its data, which is iterable, gets returned to the second-to-last element. The second-to-last element yields every element of that data in turn, and then returns its own data to the third-to-last element. The third-to-last element yields every element of that data in turn, and so on, until finally you get to the first element of the list. Every single yield statement thus far has passed one element up the chain, recursively, and so that inner for loop for the first element has yielded 36 values so far. Finally, all the later elements in the list are done passing values through, and so the first element gets to the last statement of the function and yields its own data.
But there's nothing left to catch that yielded data and parse it by individual element, so it gets printed as the list it was in the first place. Or, at least, that is for my example presented above.
In your case, it's more straightforward, because when you iterate over a string each item is still a string. But it's the same thing on a smaller scale:
get_reverse_iterator() is called on the root node of lst - The root node (I'll call it
NodeA) has a .next get_reverse_iterator() is called on the next node, which I'll call NodeB NodeB has a .next get_reverse_iterator() is called on the next node, which I'll call NodeC NodeC does not have a .next get_reverse_iterator(NodeC) skips the for loop and yields NodeC.data, which is 'c'` get_reverse_iterator(NodeB) catches 'c' inside the for loop and yields it get_reverse_iterator(NodeA) catches 'c' inside the for loop and yields it 'c' gets assigned to x, and it gets printed. - The next iteration of the outer loop happens, and execution returns to
get_reverse_iterator(NodeB) - The
for loop ends, because get_reverse_iterator(NodeC) has stopped yielding things get_reverse_iterator(NodeB) ends the for loop, exits the if block, and finally yields NodeB.data, which is 'b' get_reverse_iterator(NodeA) catches 'b' inside the for loop and yields it 'b' gets assigned to x, and it gets printed. - The next iteration of the outer loop happens, and execution returns to
get_reverse_iterator(NodeA) - The
for loop ends, because get_reverse_iterator(NodeC) has stopped yielding things get_reverse_iterator(NodeA) ends the for loop, exits the if block, and finally yields NodeA.data, which is 'a' 'a' gets assigned to x, and it gets printed - The outer
for loop finishes, as get_reverse_iterator(NodeA) has stopped yielding things.
yield from get_reverse_iterator(head.next).