Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I am confused about why different looping constructs behave so differently when used with a simple generator. Consider the following code:

example_list = [1, 2, 3, 4, 5]
for_count = 0
next_count = 0
while_count = 0

def consumer(iterable):
    for item in iterable:
        yield    
    return

for item in consumer(example_list):
    print("For Count: {0}".format(for_count))
    for_count += 1

# First while loop
while consumer(example_list).next():
    print("Next Count: {0}".format(next_count))
    next_count += 1

# Second while loop
while consumer(example_list):
    print("While Count: {0}".format(while_count))
    while_count += 1
    if while_count > 10: # add contrived limit for infinite loop
        break

The output for this code is:

For Count: 0
For Count: 1
For Count: 2
For Count: 3
For Count: 4
While Count: 0
While Count: 1
While Count: 2
While Count: 3
While Count: 4
While Count: 5
While Count: 6
While Count: 7
While Count: 8
While Count: 9
While Count: 10

I would appreciate help understanding the following:

  1. How does the for loop know when to end but not the second while loop? I expected both while loops to exit immediately since None is yielded.
  2. Why doesn't using .next() raise an exception? The consumer routine isn't a class with a __next__() method defined, so does it magically appear when you use the yield keyword?
  3. Why is it that if I change consumer to yield item, the first while loop become infinite like the second one?
  4. If I change consumer to simply return instead of yielding anything, the second while loop exits immediate instead of becoming infinite. I've been under the impression that a yield is essentially a return that you can resume from. Why are they treated differently by a while loop?
share|improve this question

2 Answers 2

up vote 5 down vote accepted

The for loop

Your first for loop works as expected. Update: Mark Ransom noted that your yield is not accompanied by the expected item, so it just returns [None, None, None, None, None] rather than [1, 2, 3, 4, 5] - but it still iterates over the list.

The first while loop

The very same commentator also noticed that the first while loop never starts because 0 is a False-equivalent in Python.

The second while loop

In the second while loop, you are testing the value of consumer(example_list). This is the generator object itself, not the values return by its next(). The object itself never equals None, or any other False equivalent - so your loop never ends.

This can be seen by printing the value of consumer(example_list), your while condition, within the loop:

>>> while_count=0
>>> while consumer(example_list):
...     print while_count, consumer(example_list)
...     while_count += 1
...     if while_count > 10:
...         break

Giving:

0 <generator object consumer at 0x1044a1b90>
1 <generator object consumer at 0x1044a1b90>
2 <generator object consumer at 0x1044a1b90>
3 <generator object consumer at 0x1044a1b90>
4 <generator object consumer at 0x1044a1b90>
5 <generator object consumer at 0x1044a1b90>
6 <generator object consumer at 0x1044a1b90>
7 <generator object consumer at 0x1044a1b90>
8 <generator object consumer at 0x1044a1b90>
9 <generator object consumer at 0x1044a1b90>
10 <generator object consumer at 0x1044a1b90>

The second item is the object, which never equals None.

share|improve this answer
1  
And the middle example returns 0 the first time, which does evaluate as False thus terminating that loop immediately. –  Mark Ransom Aug 27 at 18:59
    
@MarkRansom True, good point. Didn't event look at that loop. –  Adam Matan Aug 27 at 19:00
1  
Actually I was mistaken, since there's nothing after the yield it returns None not 0, which also evaluates as False. So I was right but for the wrong reason. –  Mark Ransom Aug 27 at 19:14
    
@MarkRansom - Splendid. BTW, working with breakpoints and a debugger would have made it clear. –  Adam Matan Aug 27 at 19:23

Answering only a subset of your question:

Your misconception with the while loops is, that a while loop does not iterate over a generator object by itself, as a for item in generator loop does.

The consumer(example_list) in your second while loop always returns a generator, which evaluates as a boolean True, hence the loops runs forever.

In the first while loop, you're testing the first yield value from that generator, which is None. Hence, the loop doesn't even start.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.